diff --git a/packages/Postgrest/Postgrest.Tests/Requests/NonJsonResponseTests.cs b/packages/Postgrest/Postgrest.Tests/Requests/NonJsonResponseTests.cs
new file mode 100644
index 00000000..e860b053
--- /dev/null
+++ b/packages/Postgrest/Postgrest.Tests/Requests/NonJsonResponseTests.cs
@@ -0,0 +1,66 @@
+using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Supabase.Postgrest;
+using Supabase.Postgrest.Attributes;
+using Supabase.Postgrest.Exceptions;
+using Supabase.Postgrest.Models;
+using WireMock.RequestBuilders;
+using WireMock.ResponseBuilders;
+using WireMock.Server;
+
+namespace Postgrest.Tests.Requests;
+
+///
+/// A 2xx body that isn't JSON throws a carrying the body and status.
+///
+[TestClass]
+[TestCategory("Contract")]
+public class NonJsonResponseTests
+{
+ private const string ProxyErrorPage = "
502 Bad Gateway";
+
+ private WireMockServer server = null!;
+ private Client client = null!;
+
+ [Table("todos")]
+ private class Todo : BaseModel
+ {
+ [PrimaryKey("id")] public int Id { get; set; }
+ [Column("name")] public string? Name { get; set; }
+ }
+
+ [TestInitialize]
+ public void SetUp()
+ {
+ this.server = WireMockServer.Start();
+ this.client = new Client(this.server.Url!, new ClientOptions());
+ }
+
+ [TestCleanup]
+ public void TearDown() => this.server.Stop();
+
+ [TestMethod]
+ public async Task Get_ShouldThrowPostgrestException_GivenNonJsonBody()
+ {
+ this.MockProxyErrorPage("/todos");
+ var act = () => this.client.Table().Get();
+ await act.Should().ThrowAsync()
+ .Where(exception => exception.Content == ProxyErrorPage && exception.StatusCode == 200,
+ "postgrest-js returns a non-JSON 2xx body as the error (#260)");
+ }
+
+ [TestMethod]
+ public async Task Rpc_ShouldThrowPostgrestException_GivenNonJsonBody()
+ {
+ this.MockProxyErrorPage("/rpc/echo");
+ var act = () => this.client.Rpc("echo");
+ await act.Should().ThrowAsync()
+ .Where(exception => exception.Content == ProxyErrorPage && exception.StatusCode == 200,
+ "postgrest-js handles rpc responses the same way (#260)");
+ }
+
+ private void MockProxyErrorPage(string path) =>
+ this.server.Given(Request.Create().WithPath(path))
+ .RespondWith(Response.Create().WithStatusCode(200).WithBody(ProxyErrorPage));
+}
diff --git a/packages/Postgrest/Postgrest/Client.cs b/packages/Postgrest/Postgrest/Client.cs
index d0063531..04ce8320 100644
--- a/packages/Postgrest/Postgrest/Client.cs
+++ b/packages/Postgrest/Postgrest/Client.cs
@@ -108,7 +108,7 @@ public T Attach(T model) where T : BaseModel
{
var response = await this.Rpc(procedureName, parameters);
- return string.IsNullOrEmpty(response.Content) ? default : JsonSerializer.Deserialize(response.Content!, SerializerSettings(this.Options));
+ return string.IsNullOrEmpty(response.Content) ? default : Helpers.DeserializeBody(response, SerializerSettings(this.Options));
}
///
diff --git a/packages/Postgrest/Postgrest/Helpers.cs b/packages/Postgrest/Postgrest/Helpers.cs
index fecdcfa5..19eb4a7b 100644
--- a/packages/Postgrest/Postgrest/Helpers.cs
+++ b/packages/Postgrest/Postgrest/Helpers.cs
@@ -175,6 +175,26 @@ HttpRequestMessage CreateRequest()
}
}
+ ///
+ /// Deserializes a 2xx body and throws a PostgrestException when it can't be read, e.g. a proxy's HTML page.
+ ///
+ internal static T? DeserializeBody(BaseResponse response, JsonSerializerOptions options)
+ {
+ try
+ {
+ return JsonSerializer.Deserialize(response.Content!, options);
+ }
+ catch (JsonException e)
+ {
+ throw new PostgrestException(response.Content, e)
+ {
+ Content = response.Content,
+ Response = response.ResponseMessage,
+ StatusCode = (int) (response.ResponseMessage?.StatusCode ?? 0)
+ };
+ }
+ }
+
///
/// Prepares the request with appropriate HTTP headers expected by Postgrest.
///
diff --git a/packages/Postgrest/Postgrest/Responses/ModeledResponse.cs b/packages/Postgrest/Postgrest/Responses/ModeledResponse.cs
index 9f21726c..ef71e2f1 100644
--- a/packages/Postgrest/Postgrest/Responses/ModeledResponse.cs
+++ b/packages/Postgrest/Postgrest/Responses/ModeledResponse.cs
@@ -38,7 +38,7 @@ public ModeledResponse(BaseResponse baseResponse, JsonSerializerOptions serializ
if (!shouldParse || string.IsNullOrEmpty(this.Content)) return;
- var token = JsonNode.Parse(this.Content!);
+ var token = Helpers.DeserializeBody(this, serializerSettings);
switch (token)
{