8000 Wrap Deserialization exceptions as an ApiException by clairernovotny · Pull Request #1014 · reactiveui/refit · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Wrap Deserialization exceptions as an ApiException #1014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions Refit.Tests/ResponseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,57 @@ public async Task ValidationApiException_HydratesBaseContent()
var actualBaseException = actualException as ApiException;
Assert.Equal(expectedContent, actualBaseException.Content);
}


[Fact]
public async Task WithHtmlResponse_ShouldReturnApiException()
{
const string htmlResponse = "<html><body>Hello world</body></html>";
var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(htmlResponse)
};
expectedResponse.Content.Headers.Clear();

mockHandler.Expect(HttpMethod.Get, "http://api/aliasTest")
.Respond(req => expectedResponse);

var actualException = await Assert.ThrowsAsync<ApiException>(() => fixture.GetTestObject());

Assert.IsType<System.Text.Json.JsonException>(actualException.InnerException);
Assert.NotNull(actualException.Content);
Assert.Equal(htmlResponse, actualException.Content);
}

[Fact]
public async Task WithNonJsonResponseUsingNewtonsoftJsonContentSerializer_ShouldReturnApiException()
{
var settings = new RefitSettings
{
HttpMessageHandlerFactory = () => mockHandler,
ContentSerializer = new NewtonsoftJsonContentSerializer()
};

var newtonSoftFixture = RestService.For<IMyAliasService>("http://api", settings);

const string nonJsonResponse = "bad response";
var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(nonJsonResponse)
};
expectedResponse.Content.Headers.Clear();

mockHandler.Expect(HttpMethod.Get, "http://api/aliasTest")
.Respond(req => expectedResponse);

var actualException = await Assert.ThrowsAsync<ApiException>(() => newtonSoftFixture.GetTestObject());

Assert.IsType<JsonReaderException>(actualException.InnerException);
Assert.NotNull(actualException.Content);
Assert.Equal(nonJsonResponse, actualException.Content);
}


}

public sealed class ThrowOnGetLengthMemoryStream : MemoryStream
Expand Down
16 changes: 8 additions & 8 deletions Refit/ApiException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ public class ApiException : Exception
public bool HasContent => !string.IsNullOrWhiteSpace(Content);
public RefitSettings RefitSettings { get; }

protected ApiException(HttpRequestMessage message, HttpMethod httpMethod, string? content, HttpStatusCode statusCode, string? reasonPhrase, HttpResponseHeaders headers, RefitSettings refitSettings) :
this(CreateMessage(statusCode, reasonPhrase), message, httpMethod, content, statusCode, reasonPhrase, headers, refitSettings)
protected ApiException(HttpRequestMessage message, HttpMethod httpMethod, string? content, HttpStatusCode statusCode, string? reasonPhrase, HttpResponseHeaders headers, RefitSettings refitSettings, Exception? innerException = null) :
this(CreateMessage(statusCode, reasonPhrase), message, httpMethod, content, statusCode, reasonPhrase, headers, refitSettings, innerException)
{
}

protected ApiException(string exceptionMessage, HttpRequestMessage message, HttpMethod httpMethod, string? content, HttpStatusCode statusCode, string? reasonPhrase, HttpResponseHeaders headers, RefitSettings refitSettings) :
base(exceptionMessage)
protected ApiException(string exceptionMessage, HttpRequestMessage message, HttpMethod httpMethod, string? content, HttpStatusCode statusCode, string? reasonPhrase, HttpResponseHeaders headers, RefitSettings refitSettings, Exception? innerException = null) :
base(exceptionMessage, innerException)
{
RequestMessage = message;
HttpMethod = httpMethod;
Expand All @@ -43,18 +43,18 @@ await RefitSettings.ContentSerializer.DeserializeAsync<T>(new StringContent(Cont
default;

#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods
public static Task<ApiException> Create(HttpRequestMessage message, HttpMethod httpMethod, HttpResponseMessage response, RefitSettings refitSettings)
public static Task<ApiException> Create(HttpRequestMessage message, HttpMethod httpMethod, HttpResponseMessage response, RefitSettings refitSettings, Exception? innerException = null)
#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods
{
var exceptionMessage = CreateMessage(response.StatusCode, response.ReasonPhrase);
return Create(exceptionMessage, message, httpMethod, response, refitSettings);
return Create(exceptionMessage, message, httpMethod, response, refitSettings, innerException);
}

#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods
public static async Task<ApiException> Create(string exceptionMessage, HttpRequestMessage message, HttpMethod httpMethod, HttpResponseMessage response, RefitSettings refitSettings)
public static async Task<ApiException> Create(string exceptionMessage, HttpRequestMessage message, HttpMethod httpMethod, HttpResponseMessage response, RefitSettings refitSettings, Exception? innerException = null)
#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods
{
var exception = new ApiException(exceptionMessage, message, httpMethod, null, response.StatusCode, response.ReasonPhrase, response.Headers, refitSettings);
var exception = new ApiException(exceptionMessage, message, httpMethod, null, response.StatusCode, response.ReasonPhrase, response.Headers, refitSettings, innerException);

if (response.Content == null)
{
Expand Down
61 changes: 35 additions & 26 deletions Refit/RequestBuilderImplementation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ async Task AddMultipartItemAsync(MultipartFormDataContent multiPartContent, stri
e = await settings.ExceptionFactory(resp).ConfigureAwait(false);
}


if (restMethod.IsApiResponse)
{
// Only attempt to deserialize content if no error present for backward-compatibility
Expand All @@ -269,6 +270,7 @@ async Task AddMultipartItemAsync(MultipartFormDataContent multiPartContent, stri
}
else
return await DeserializeContentAsync<T>(resp, content, ct).ConfigureAwait(false);

}
finally
{
Expand All @@ -286,35 +288,42 @@ async Task AddMultipartItemAsync(MultipartFormDataContent multiPartContent, stri

async Task<T?> DeserializeContentAsync<T>(HttpResponseMessage resp, HttpContent content, CancellationToken cancellationToken)
{
T? result;
if (typeof(T) == typeof(HttpResponseMessage))
{
// NB: This double-casting manual-boxing hate crime is the only way to make
// this work without a 'class' generic constraint. It could blow up at runtime
// and would be A Bad Idea if we hadn't already vetted the return type.
result = (T)(object)resp;
}
else if (typeof(T) == typeof(HttpContent))
{
result = (T)(object)content;
}
else if (typeof(T) == typeof(Stream))
{
var stream = (object)await content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
result = (T)stream;
}
else if (typeof(T) == typeof(string))
try
{
using var stream = await content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var reader = new StreamReader(stream);
var str = (object)await reader.ReadToEndAsync().ConfigureAwait(false);
result = (T)str;
T? result;
if (typeof(T) == typeof(HttpResponseMessage))
{
// NB: This double-casting manual-boxing hate crime is the only way to make
// this work without a 'class' generic constraint. It could blow up at runtime
// and would be A Bad Idea if we hadn't already vetted the return type.
result = (T)(object)resp;
}
else if (typeof(T) == typeof(HttpContent))
{
result = (T)(object)content;
}
else if (typeof(T) == typeof(Stream))
{
var stream = (object)await content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
result = (T)stream;
}
else if (typeof(T) == typeof(string))
{
using var stream = await content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var reader = new StreamReader(stream);
var str = (object)await reader.ReadToEndAsync().ConfigureAwait(false);
result = (T)str;
}
else
{
result = await serializer.DeserializeAsync<T>(content, cancellationToken).ConfigureAwait(false);
}
return result;
}
else
catch(Exception ex) // wrap the exception as an ApiException
{
result = await serializer.DeserializeAsync<T>(content, cancellationToken).ConfigureAwait(false);
}
return result;
throw await ApiException.Create("An error occured deserializing the response.", resp.RequestMessage!, resp.RequestMessage!.Method, resp, settings, ex);
}
}

List<KeyValuePair<string, object?>> BuildQueryMap(object? @object, string? delimiter = null, RestMethodParameterInfo? parameterInfo = null)
Expand Down
0