Wrap Deserialization exceptions as an ApiException
This commit is contained in:
Родитель
b29353403f
Коммит
1e62b20fe6
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче