Wrap Deserialization exceptions as an ApiException

This commit is contained in:
Claire Novotny 2020-12-09 10:19:43 -05:00
Родитель b29353403f
Коммит 1e62b20fe6
Не удалось извлечь подпись
3 изменённых файлов: 94 добавлений и 34 удалений

Просмотреть файл

@ -248,6 +248,57 @@ namespace Refit.Tests
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

Просмотреть файл

@ -21,13 +21,13 @@ namespace Refit
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;
@ -43,18 +43,18 @@ namespace Refit
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)
{

Просмотреть файл

@ -253,6 +253,7 @@ namespace Refit
e = await settings.ExceptionFactory(resp).ConfigureAwait(false);
}
if (restMethod.IsApiResponse)
{
// Only attempt to deserialize content if no error present for backward-compatibility
@ -269,6 +270,7 @@ namespace Refit
}
else
return await DeserializeContentAsync<T>(resp, content, ct).ConfigureAwait(false);
}
finally
{
@ -286,35 +288,42 @@ namespace Refit
async Task<T?> DeserializeContentAsync<T>(HttpResponseMessage resp, HttpContent content, CancellationToken cancellationToken)
{
T? result;
if (typeof(T) == typeof(HttpResponseMessage))
try
{
// 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;
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 if (typeof(T) == typeof(HttpContent))
catch(Exception ex) // wrap the exception as an ApiException
{
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;
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)