Fast-endpoints: global exception handling using Results pattern

488 views Asked by At

I am trying to integrate fast-endpoints with Results (nuget: LanguageExt.Core) for the purpose of moving away from throw-based exception validation. My issue is that while the lib allows me to do something like

public class MyEndpoint : Endpoint<MyRequest,Results<Ok<MyResponse>, NotFound,ProblemDetails>>

this forces me to if-else on every endpoint to output the different return values. I am more interested in doing something like

public class MyEndpoint : Endpoint<MyRequest, Result<MyResponse>>

and i would then have a global post-processor to handle all faulted responses (which would be exceptions carried over from the domain, etc) and leave the endpoint to handle the happy path.

My technical hurdle with this is that, inside the post-processor handler i don't know how i would get a hold of this Result object. from the signature of the post-processor handler

public Task PostProcessAsync(IPostProcessorContext context, CancellationToken ct)

the context has a Response property but its object? so i dont know how to proceed.

1

There are 1 answers

0
Dĵ ΝιΓΞΗΛψΚ On BEST ANSWER

i haven't used the results pattern from LanguageExt.Core. but when using Ardalis.Result package, we use an extension method for sending the response as shown below. it then becomes the responsibility of the SendResponseAsync() method to check the result to see if it's in a faulted state or not and send the appropriate happy or sad response. the endpoint handler code is free from if-else code this way.

sealed class Request
{
    public bool IsHappyPath { get; set; }
}

sealed class Response
{
    public string Message { get; set; }
}

sealed class TestEndpoint : Endpoint<Request, Response>
{
    public override void Configure()
    {
        Get("test/{IsHappyPath}");
        AllowAnonymous();
    }

    public override async Task HandleAsync(Request r, CancellationToken c)
    {
        var result = HelloService.SayHello(r.IsHappyPath);

        await this.SendResponse(result, r => new Response
        {
            Message = r.Value
        });
    }
}

sealed class HelloService
{
    public static Result<string> SayHello(bool isHappyPath)
    {
        if (!isHappyPath)
        {
            return Result<string>.Invalid(new List<ValidationError>
            {
                new ValidationError
                {
                    Identifier = nameof(Request.IsHappyPath),
                    ErrorMessage = "I am unhappy!"
                }
            });
        }
        return Result<string>.Success("hello world...");
    }
}

static class ArdalisResultsExtensions
{
    public static async Task SendResponse<TResult, TResponse>(this IEndpoint ep, TResult result, Func<TResult, TResponse> mapper) where TResult : Ardalis.Result.IResult
    {
        switch (result.Status)
        {
            case ResultStatus.Ok:
                await ep.HttpContext.Response.SendAsync(mapper(result));
                break;

            case ResultStatus.Invalid:
                result.ValidationErrors.ForEach(e =>
                    ep.ValidationFailures.Add(new(e.Identifier, e.ErrorMessage)));

                await ep.HttpContext.Response.SendErrorsAsync(ep.ValidationFailures);
                break;
        }
    }
}