Last active
October 1, 2024 09:00
-
-
Save dj-nitehawk/66cba78a1a3a1e0799d87d67d8aa14bd to your computer and use it in GitHub Desktop.
Response sending post-processor with ErrorOr package
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
var bld = WebApplication.CreateBuilder(args); | |
bld.Services | |
.SwaggerDocument() | |
.AddFastEndpoints(); | |
var app = bld.Build(); | |
app.UseFastEndpoints( | |
c => | |
{ | |
c.Errors.UseProblemDetails(); | |
c.Endpoints.Configurator = | |
ep => | |
{ | |
if (ep.ResDtoType.IsAssignableTo(typeof(IErrorOr))) | |
{ | |
ep.DontAutoSendResponse(); | |
ep.PostProcessor<ResponseSender>(Order.After); | |
ep.Description( | |
b => b.ClearDefaultProduces() | |
.Produces(200, ep.ResDtoType.GetGenericArguments()[0]) | |
.ProducesProblemDetails()); | |
} | |
}; | |
}) | |
.UseSwaggerGen(); | |
app.Run(); |
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
sealed class Request | |
{ | |
public bool IsHappyPath { get; set; } | |
} | |
sealed class Response | |
{ | |
public string Message { get; set; } | |
} | |
sealed class TestEndpoint : Endpoint<Request, ErrorOr<Response>> //set response type to ErrorOr<T> | |
{ | |
public override void Configure() | |
{ | |
Get("test/{IsHappyPath}"); | |
AllowAnonymous(); | |
} | |
public override Task<ErrorOr<Response>> ExecuteAsync(Request r, CancellationToken ct) | |
=> Task.FromResult(HelloService.SayHello(r.IsHappyPath)); //return a ErrorOr<T> | |
} |
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
sealed class HelloService | |
{ | |
public static ErrorOr<Response> SayHello(bool isHappyPath) | |
{ | |
if (!isHappyPath) | |
return Error.Validation("isHappyPath", "You have chosen to be unhappy!"); | |
return new Response { Message = "You have chosen to be happy!" }; | |
} | |
} |
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
sealed class ResponseSender : IGlobalPostProcessor | |
{ | |
public Task PostProcessAsync(IPostProcessorContext ctx, CancellationToken ct) | |
{ | |
if (ctx.HttpContext.ResponseStarted() || ctx.Response is not IErrorOr errorOr) | |
return Task.CompletedTask; | |
if (!errorOr.IsError) | |
return ctx.HttpContext.Response.SendAsync(GetValueFromErrorOr(errorOr), cancellation: ct); | |
if (errorOr.Errors?.All(e => e.Type == ErrorType.Validation) is true) | |
{ | |
return ctx.HttpContext.Response.SendErrorsAsync( | |
failures: [..errorOr.Errors.Select(e => new ValidationFailure(e.Code, e.Description))], | |
cancellation: ct); | |
} | |
var problem = errorOr.Errors?.FirstOrDefault(e => e.Type != ErrorType.Validation); | |
switch (problem?.Type) | |
{ | |
case ErrorType.Conflict: | |
return ctx.HttpContext.Response.SendAsync("Duplicate submission!", 409, cancellation: ct); | |
case ErrorType.NotFound: | |
return ctx.HttpContext.Response.SendNotFoundAsync(ct); | |
case ErrorType.Unauthorized: | |
return ctx.HttpContext.Response.SendUnauthorizedAsync(ct); | |
case ErrorType.Forbidden: | |
return ctx.HttpContext.Response.SendForbiddenAsync(ct); | |
case null: | |
throw new InvalidOperationException(); | |
} | |
return Task.CompletedTask; | |
} | |
//cached compiled expressions for reading ErrorOr<T>.Value property | |
static readonly ConcurrentDictionary<Type, Func<object, object>> _valueAccessors = new(); | |
static object GetValueFromErrorOr(object errorOr) | |
{ | |
ArgumentNullException.ThrowIfNull(errorOr); | |
var tErrorOr = errorOr.GetType(); | |
if (!tErrorOr.IsGenericType || tErrorOr.GetGenericTypeDefinition() != typeof(ErrorOr<>)) | |
throw new InvalidOperationException("The provided object is not an instance of ErrorOr<>."); | |
return _valueAccessors.GetOrAdd(tErrorOr, CreateValueAccessor)(errorOr); | |
static Func<object, object> CreateValueAccessor(Type errorOrType) | |
{ | |
var parameter = Expression.Parameter(typeof(object), "errorOr"); | |
return Expression.Lambda<Func<object, object>>( | |
Expression.Convert( | |
Expression.Property( | |
Expression.Convert(parameter, errorOrType), | |
"Value"), | |
typeof(object)), | |
parameter) | |
.Compile(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment