-
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
658962f
commit 576b6f5
Showing
6 changed files
with
201 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
using GenHTTP.Api.Content; | ||
using GenHTTP.Api.Protocol; | ||
|
||
using GenHTTP.Modules.Conversion; | ||
using GenHTTP.Modules.Conversion.Providers; | ||
using GenHTTP.Modules.Conversion.Providers.Json; | ||
|
||
namespace GenHTTP.Modules.ErrorHandling | ||
{ | ||
|
||
public sealed class StructuredErrorMapper : IErrorMapper<Exception> | ||
{ | ||
|
||
#region Supporting data structures | ||
|
||
public record class ErrorModel(ResponseStatus Status, string Message, string? StackTrace = null); | ||
|
||
#endregion | ||
|
||
#region Get-/Setters | ||
|
||
public SerializationRegistry Registry { get; } | ||
|
||
#endregion | ||
|
||
#region Initialization | ||
|
||
public StructuredErrorMapper(SerializationRegistry? registry) | ||
{ | ||
Registry = registry ?? Serialization.Default().Build(); | ||
} | ||
|
||
#endregion | ||
|
||
#region Functionality | ||
|
||
public async ValueTask<IResponse?> Map(IRequest request, IHandler handler, Exception error) | ||
{ | ||
string? stackTrace = null; | ||
|
||
if (request.Server.Development) | ||
{ | ||
stackTrace = error.StackTrace; | ||
} | ||
|
||
if (error is ProviderException e) | ||
{ | ||
var model = new ErrorModel(e.Status, error.Message, stackTrace); | ||
|
||
return await RenderAsync(request, model); | ||
} | ||
else | ||
{ | ||
var model = new ErrorModel(ResponseStatus.InternalServerError, error.Message, stackTrace); | ||
|
||
return await RenderAsync(request, model); | ||
} | ||
} | ||
|
||
public async ValueTask<IResponse?> GetNotFound(IRequest request, IHandler handler) | ||
{ | ||
var model = new ErrorModel(ResponseStatus.NotFound, "The requested resource does not exist on this server"); | ||
|
||
return await RenderAsync(request, model); | ||
} | ||
|
||
private async ValueTask<IResponse> RenderAsync(IRequest request, ErrorModel model) | ||
{ | ||
var format = Registry.GetSerialization(request) ?? new JsonFormat(); | ||
|
||
var response = await format.SerializeAsync(request, model); | ||
|
||
response.Status(model.Status); | ||
|
||
return response.Build(); | ||
} | ||
|
||
#endregion | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
Testing/Acceptance/Modules/ErrorHandling/StructuredErrorMapperTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
using System; | ||
using System.Net; | ||
using System.Threading.Tasks; | ||
|
||
using GenHTTP.Api.Content; | ||
using GenHTTP.Api.Protocol; | ||
|
||
using GenHTTP.Modules.ErrorHandling; | ||
using GenHTTP.Modules.Functional; | ||
|
||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
||
namespace GenHTTP.Testing.Acceptance.Modules.ErrorHandling | ||
{ | ||
|
||
[TestClass] | ||
public sealed class StructuredErrorMapperTests | ||
{ | ||
|
||
[TestMethod] | ||
public async Task TestNotFound() | ||
{ | ||
using var host = TestHost.Run(Inline.Create()); | ||
|
||
using var response = await host.GetResponseAsync(); | ||
|
||
await response.AssertStatusAsync(HttpStatusCode.NotFound); | ||
|
||
var model = await response.GetContentAsync<StructuredErrorMapper.ErrorModel>(); | ||
|
||
Assert.AreEqual(ResponseStatus.NotFound, model.Status); | ||
|
||
Assert.IsNull(model.StackTrace); | ||
} | ||
|
||
[TestMethod] | ||
public async Task TestGeneralError() | ||
{ | ||
var handler = Inline.Create() | ||
.Get(() => DoThrow(new Exception("Oops"))); | ||
|
||
using var host = TestHost.Run(handler); | ||
|
||
using var response = await host.GetResponseAsync(); | ||
|
||
await response.AssertStatusAsync(HttpStatusCode.InternalServerError); | ||
|
||
var model = await response.GetContentAsync<StructuredErrorMapper.ErrorModel>(); | ||
|
||
Assert.AreEqual(ResponseStatus.InternalServerError, model.Status); | ||
|
||
Assert.IsNotNull(model.StackTrace); | ||
} | ||
|
||
[TestMethod] | ||
public async Task TestProviderError() | ||
{ | ||
var handler = Inline.Create() | ||
.Get(() => DoThrow(new ProviderException(ResponseStatus.Locked, "Locked up!"))); | ||
|
||
using var host = TestHost.Run(handler); | ||
|
||
using var response = await host.GetResponseAsync(); | ||
|
||
await response.AssertStatusAsync(HttpStatusCode.Locked); | ||
|
||
var model = await response.GetContentAsync<StructuredErrorMapper.ErrorModel>(); | ||
|
||
Assert.AreEqual(ResponseStatus.Locked, model.Status); | ||
|
||
Assert.IsNotNull(model.StackTrace); | ||
} | ||
|
||
[TestMethod] | ||
public async Task TestNoTraceInProduction() | ||
{ | ||
var handler = Inline.Create() | ||
.Get(() => DoThrow(new Exception("Oops"))); | ||
|
||
using var host = TestHost.Run(handler, development: false); | ||
|
||
using var response = await host.GetResponseAsync(); | ||
|
||
await response.AssertStatusAsync(HttpStatusCode.InternalServerError); | ||
|
||
var model = await response.GetContentAsync<StructuredErrorMapper.ErrorModel>(); | ||
|
||
Assert.IsNull(model.StackTrace); | ||
} | ||
|
||
private static void DoThrow(Exception e) | ||
{ | ||
throw e; | ||
} | ||
|
||
} | ||
|
||
} |