Skip to content

Commit

Permalink
Merge pull request #900 from Dreamescaper/customize_exceptions
Browse files Browse the repository at this point in the history
Add ability to provide custom ExceptionFactory
  • Loading branch information
clairernovotny authored Sep 5, 2020
2 parents d9fa9d9 + 0abca64 commit 2e94493
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 15 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,17 @@ catch (ApiException exception)
// ...
```

You can also override default exceptions behavior by providing custom exception factory in `RefitSettings`. For example, you can suppress all exceptions with the following:

```csharp
var nullTask = Task.FromResult<Exception>(null);

var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com",
new RefitSettings {
ExceptionFactory = httpResponse => nullTask;
});
```

### MSBuild configuration

- `RefitDisableGenerateRefitStubs`: This property allows for other Roslyn-based source generators to disable the Refit's stubs generation during they own generation phase.
111 changes: 111 additions & 0 deletions Refit.Tests/ExceptionFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

using Refit; // for the code gen

using RichardSzalay.MockHttp;

using Xunit;

namespace Refit.Tests
{
public class ExceptionFactoryTests
{
public interface IMyService
{
[Get("/get-with-result")]
Task<string> GetWithResult();

[Put("/put-without-result")]
Task PutWithoutResult();
}

[Fact]
public async Task ProvideFactoryWhichAlwaysReturnsNull_WithResult()
{
var handler = new MockHttpMessageHandler();
var settings = new RefitSettings()
{
HttpMessageHandlerFactory = () => handler,
ExceptionFactory = _ => Task.FromResult<Exception>(null)
};

handler.Expect(HttpMethod.Get, "http://api/get-with-result")
.Respond(HttpStatusCode.NotFound, new StringContent("error-result"));

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

var result = await fixture.GetWithResult();

handler.VerifyNoOutstandingExpectation();

Assert.Equal("error-result", result);
}

[Fact]
public async Task ProvideFactoryWhichAlwaysReturnsNull_WithoutResult()
{
var handler = new MockHttpMessageHandler();
var settings = new RefitSettings()
{
HttpMessageHandlerFactory = () => handler,
ExceptionFactory = _ => Task.FromResult<Exception>(null)
};

handler.Expect(HttpMethod.Put, "http://api/put-without-result")
.Respond(HttpStatusCode.NotFound);

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

await fixture.PutWithoutResult();

handler.VerifyNoOutstandingExpectation();
}

[Fact]
public async Task ProvideFactoryWhichAlwaysReturnsException_WithResult()
{
var handler = new MockHttpMessageHandler();
var exception = new Exception("I like to fail");
var settings = new RefitSettings()
{
HttpMessageHandlerFactory = () => handler,
ExceptionFactory = _ => Task.FromResult<Exception>(exception)
};

handler.Expect(HttpMethod.Get, "http://api/get-with-result")
.Respond(HttpStatusCode.OK, new StringContent("success-result"));

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

var thrownException = await Assert.ThrowsAsync<Exception>(() => fixture.GetWithResult());
Assert.Equal(exception, thrownException);

handler.VerifyNoOutstandingExpectation();
}

[Fact]
public async Task ProvideFactoryWhichAlwaysReturnsException_WithoutResult()
{
var handler = new MockHttpMessageHandler();
var exception = new Exception("I like to fail");
var settings = new RefitSettings()
{
HttpMessageHandlerFactory = () => handler,
ExceptionFactory = _ => Task.FromResult<Exception>(exception)
};

handler.Expect(HttpMethod.Put, "http://api/put-without-result")
.Respond(HttpStatusCode.OK);

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

var thrownException = await Assert.ThrowsAsync<Exception>(() => fixture.PutWithoutResult());
Assert.Equal(exception, thrownException);

handler.VerifyNoOutstandingExpectation();
}
}
}
46 changes: 46 additions & 0 deletions Refit.Tests/RefitStubs.Net46.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1887,6 +1887,52 @@ Task<string> AuthenticatedClientHandlerTests.IMyAuthenticatedService.GetAuthenti
}
}

namespace Refit.Tests
{
using global::System;
using global::System.Net;
using global::System.Net.Http;
using global::System.Threading.Tasks;
using global::Refit;
using global::RichardSzalay.MockHttp;
using global::Xunit;

/// <inheritdoc />
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Diagnostics.DebuggerNonUserCode]
[Preserve]
[global::System.Reflection.Obfuscation(Exclude=true)]
partial class AutoGeneratedExceptionFactoryTestsIMyService : ExceptionFactoryTests.IMyService
{
/// <inheritdoc />
public HttpClient Client { get; protected set; }
readonly IRequestBuilder requestBuilder;

/// <inheritdoc />
public AutoGeneratedExceptionFactoryTestsIMyService(HttpClient client, IRequestBuilder requestBuilder)
{
Client = client;
this.requestBuilder = requestBuilder;
}

/// <inheritdoc />
Task<string> ExceptionFactoryTests.IMyService.GetWithResult()
{
var arguments = new object[] { };
var func = requestBuilder.BuildRestResultFuncForMethod("GetWithResult", new Type[] { });
return (Task<string>)func(Client, arguments);
}

/// <inheritdoc />
Task ExceptionFactoryTests.IMyService.PutWithoutResult()
{
var arguments = new object[] { };
var func = requestBuilder.BuildRestResultFuncForMethod("PutWithoutResult", new Type[] { });
return (Task)func(Client, arguments);
}
}
}

namespace Refit.Tests
{
using global::System.Threading.Tasks;
Expand Down
46 changes: 46 additions & 0 deletions Refit.Tests/RefitStubs.NetCore2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1887,6 +1887,52 @@ Task<string> AuthenticatedClientHandlerTests.IMyAuthenticatedService.GetAuthenti
}
}

namespace Refit.Tests
{
using global::System;
using global::System.Net;
using global::System.Net.Http;
using global::System.Threading.Tasks;
using global::Refit;
using global::RichardSzalay.MockHttp;
using global::Xunit;

/// <inheritdoc />
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Diagnostics.DebuggerNonUserCode]
[Preserve]
[global::System.Reflection.Obfuscation(Exclude=true)]
partial class AutoGeneratedExceptionFactoryTestsIMyService : ExceptionFactoryTests.IMyService
{
/// <inheritdoc />
public HttpClient Client { get; protected set; }
readonly IRequestBuilder requestBuilder;

/// <inheritdoc />
public AutoGeneratedExceptionFactoryTestsIMyService(HttpClient client, IRequestBuilder requestBuilder)
{
Client = client;
this.requestBuilder = requestBuilder;
}

/// <inheritdoc />
Task<string> ExceptionFactoryTests.IMyService.GetWithResult()
{
var arguments = new object[] { };
var func = requestBuilder.BuildRestResultFuncForMethod("GetWithResult", new Type[] { });
return (Task<string>)func(Client, arguments);
}

/// <inheritdoc />
Task ExceptionFactoryTests.IMyService.PutWithoutResult()
{
var arguments = new object[] { };
var func = requestBuilder.BuildRestResultFuncForMethod("PutWithoutResult", new Type[] { });
return (Task)func(Client, arguments);
}
}
}

namespace Refit.Tests
{
using global::System.Threading.Tasks;
Expand Down
46 changes: 46 additions & 0 deletions Refit.Tests/RefitStubs.NetCore3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1887,6 +1887,52 @@ Task<string> AuthenticatedClientHandlerTests.IMyAuthenticatedService.GetAuthenti
}
}

namespace Refit.Tests
{
using global::System;
using global::System.Net;
using global::System.Net.Http;
using global::System.Threading.Tasks;
using global::Refit;
using global::RichardSzalay.MockHttp;
using global::Xunit;

/// <inheritdoc />
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Diagnostics.DebuggerNonUserCode]
[Preserve]
[global::System.Reflection.Obfuscation(Exclude=true)]
partial class AutoGeneratedExceptionFactoryTestsIMyService : ExceptionFactoryTests.IMyService
{
/// <inheritdoc />
public HttpClient Client { get; protected set; }
readonly IRequestBuilder requestBuilder;

/// <inheritdoc />
public AutoGeneratedExceptionFactoryTestsIMyService(HttpClient client, IRequestBuilder requestBuilder)
{
Client = client;
this.requestBuilder = requestBuilder;
}

/// <inheritdoc />
Task<string> ExceptionFactoryTests.IMyService.GetWithResult()
{
var arguments = new object[] { };
var func = requestBuilder.BuildRestResultFuncForMethod("GetWithResult", new Type[] { });
return (Task<string>)func(Client, arguments);
}

/// <inheritdoc />
Task ExceptionFactoryTests.IMyService.PutWithoutResult()
{
var arguments = new object[] { };
var func = requestBuilder.BuildRestResultFuncForMethod("PutWithoutResult", new Type[] { });
return (Task)func(Client, arguments);
}
}
}

namespace Refit.Tests
{
using global::System.Threading.Tasks;
Expand Down
19 changes: 16 additions & 3 deletions Refit/ApiException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ public class ApiException : Exception
public RefitSettings RefitSettings { get; set; }

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

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

#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods
public static async Task<ApiException> Create(HttpRequestMessage message, HttpMethod httpMethod, HttpResponseMessage response, RefitSettings refitSettings = null)
public static Task<ApiException> Create(HttpRequestMessage message, HttpMethod httpMethod, HttpResponseMessage response, RefitSettings refitSettings = 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);
}

#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 = null)
#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods
{
var exception = new ApiException(message, httpMethod, response.StatusCode, response.ReasonPhrase, response.Headers, refitSettings);
var exception = new ApiException(exceptionMessage, message, httpMethod, response.StatusCode, response.ReasonPhrase, response.Headers, refitSettings);

if (response.Content == null)
{
Expand Down
41 changes: 41 additions & 0 deletions Refit/RefitSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection;
using System.Runtime.Serialization;
using System.Threading.Tasks;

using Newtonsoft.Json;

namespace Refit
Expand All @@ -22,6 +23,7 @@ public RefitSettings()
ContentSerializer = new NewtonsoftJsonContentSerializer();
UrlParameterFormatter = new DefaultUrlParameterFormatter();
FormUrlEncodedParameterFormatter = new DefaultFormUrlEncodedParameterFormatter();
ExceptionFactory = new DefaultApiExceptionFactory(this).CreateAsync;
}

/// <summary>
Expand All @@ -38,6 +40,7 @@ public RefitSettings(
ContentSerializer = contentSerializer ?? throw new ArgumentNullException(nameof(contentSerializer), "The content serializer can't be null");
UrlParameterFormatter = urlParameterFormatter ?? new DefaultUrlParameterFormatter();
FormUrlEncodedParameterFormatter = formUrlEncodedParameterFormatter ?? new DefaultFormUrlEncodedParameterFormatter();
ExceptionFactory = new DefaultApiExceptionFactory(this).CreateAsync;
}

/// <summary>
Expand All @@ -55,6 +58,12 @@ public RefitSettings(
/// </summary>
public Func<HttpMessageHandler> HttpMessageHandlerFactory { get; set; }

/// <summary>
/// Supply a function to provide <see cref="Exception"/> based on <see cref="HttpResponseMessage"/>.
/// If function returns null - no exception is thrown.
/// </summary>
public Func<HttpResponseMessage, Task<Exception>> ExceptionFactory { get; set; }

[Obsolete("Set RefitSettings.ContentSerializer = new NewtonsoftJsonContentSerializer(JsonSerializerSettings) instead.", false)]
public JsonSerializerSettings JsonSerializerSettings
{
Expand Down Expand Up @@ -150,4 +159,36 @@ public virtual string Format(object parameterValue, string formatString)
enummember?.Value ?? parameterValue);
}
}

public class DefaultApiExceptionFactory
{
static readonly Task<Exception> NullTask = Task.FromResult<Exception>(null);

readonly RefitSettings refitSettings;

public DefaultApiExceptionFactory(RefitSettings refitSettings)
{
this.refitSettings = refitSettings;
}

public Task<Exception> CreateAsync(HttpResponseMessage responseMessage)
{
if (!responseMessage.IsSuccessStatusCode)
{
return CreateExceptionAsync(responseMessage, refitSettings);
}
else
{
return NullTask;
}
}

static async Task<Exception> CreateExceptionAsync(HttpResponseMessage responseMessage, RefitSettings refitSettings)
{
var requestMessage = responseMessage.RequestMessage;
var method = requestMessage.Method;

return await ApiException.Create(requestMessage, method, responseMessage, refitSettings).ConfigureAwait(false);
}
}
}
Loading

0 comments on commit 2e94493

Please sign in to comment.