GithubHelp home page GithubHelp logo

wtorricos / results Goto Github PK

View Code? Open in Web Editor NEW
4.0 2.0 0.0 77 KB

Library that allows you to write error tolerant code that can be composed

License: MIT License

C# 92.94% Batchfile 0.27% PowerShell 3.83% Shell 2.96%

results's Introduction

Results

This is a different take on Result that allows you to write error tolerant code that can be composed without losing performance.

Install it from Nuget.

Why?

We don't have a consolidated approach for error handling in dotnet. For those of us that like the Result approach we have different options:

They are all a great way to write error tolerant code. However, Results brings a slightly different approach inspired in functional programming that intents to be light and easy to get started with.

If you like the repository please feel free to open an issue or a PR for new features or improvements.

How?

Results uses source generators to generate all the boiler plate code for you, and avoid using reflection at the same time. This way you get good performance in a simple and easy to use way.

Getting Started

Results works with records, in order to create custom errors you just need to decorate your partial records with the ErrorResult attribute: (You can name your records whatever you want but the Error suffix is recommended as well as making them sealed)

[ErrorResult]
public sealed partial record OperationNotSupportedError;

By doing this the generator will extend OperationNotSupportedError and will create a generic version as well OperationNotSupportedError<T> so you can use them like this:

// Non generic version
public IResult MyOperation(int value)
{
    if (value == 0)
    {
        // Public constructors are generated as well, but Factory methods are recommended.
        return OperationNotSupportedError.Create("The value cannot be 0");
    }

    // process the value

    return Result.Success();
}

// Generic version
public IResult<int> MyOperationT(int value)
{
    if (value == 0)
    {
        // Public constructors are generated as well, but Factory methods are recommended.
        return OperationNotSupportedError<int>.Create("The value cannot be 0");
    }

    // process the value

    return Result.Success(value);
}

// You have the Cast<> method to cast errors to a specific type, for example:
IResult<int> intResult = OperationNotSupportedError<int>.Create("Invalid value");
IResult<string> strResult = intResult switch
{
    SuccessResult<int> success => success.Data.ToString(),
    IErrorResult<int> err => err.Cast<string>(),
}

As of now there are 3 operations that are supported that allow you to compose your results:

Map example:

// Works with generic results
IResult<int> intResult = Result.Success(1);
IResult<string> stringResult = intResult.Map(x => x.ToString());

// As well as with non generic results
IResult result = Result.Success();
IResult<string> stringResult = result.Map(() => "Hello World");

// You can also map tasks
Task<IResult<int>> intResult = Task.FromResult(Result.Success(1));
Task<IResult<string>> stringResult = intResult.Map(x => x.ToString());

// Or return tasks
IResult<int> intResult = Result.Success(data: 1);
Task<IResult<string>> taskResult = sut.Map(i => Task.FromResult(i.ToString(CultureInfo.InvariantCulture)));

Flat map supports all the map operations but it "flattens" a result of a result into a single result, for example:

IResult<int> intResult = Result.Success(1);
// If we use map we would get Result<Result<string>> but with flat map we get Result<string>
IResult<string> stringResult = intResult.FlatMap(x => Result.Success(x.ToString()));

Action example:

IResult<int> intResult = Result.Success(1);
intResult.Action(x => Console.WriteLine(x));

What if I want to get out of the Result world? IsSucess or similar properties are intentionally not exposed and better more well suited approaches are enforced. For example:

// Using switch or switch expressions is the recommended approach as will indirectly motivate you to handle all cases
IResult<int> intResult = Result.Success(1);
switch (intResult)
{
    case SuccessResult<int> success:
        Console.WriteLine(success.Data);
        break;
    case OperationNotSupportedError<int> error:
        Console.WriteLine(error.GetDisplayMessage());
        break;
    case IErrorResult<int> error:
        Console.WriteLine(error.GetDisplayMessage());
        break;
}

int value = intResult switch
{
    SuccessResult<int> success => success.Data,
    OperationNotSupportedError<int> error: 0, // Fallback value
    IErrorResult<int> error => throw new InvalidOperationException(error.GetDisplayMessage()),
    _ => throw new InvalidOperationException("Unknown error")
};

// You can also use pattern matching
if (intResult is SuccessResult<int> success)
{
    Console.WriteLine(success.Data);
}
else if (intResult is IErrorResult<int> error)
{
    Console.WriteLine(error.GetDisplayMessage());
}

// Don't forget you have is not at your dispossal as well
if (intResult is not SuccessResult<int>)
{
    Console.WriteLine("Not a success");
}

You can also review this complementary post for more examples.

FluentValidation

Here is a gist of an Error that plays well with the FluentValidation library.

It allows you to create an Error from a FluentValidation ValidationResult.

[ErrorResult]
public sealed partial record ValidationErrorResult<T>
{
    // Add a custom constructor to create a ValidationErrorResult<T> from a FluentValidation ValidationResult
    public ValidationErrorResult(ValidationResult validationResult)
    {
        // A valid validationResult means it's a dev error that needs to be fixed immediately
        // for that reason it's ok to throw an exception here.
        if (validationResult.IsValid)
            throw new ValidationErrorResultException();

        Message = typeof(T).Name;

        List<ErrorResultDetail> errorList = new(validationResult.Errors.Count);
        errorList.AddRange(validationResult.Errors.Select(e => new ErrorResultDetail(e.PropertyName, e.ErrorMessage)));
        Errors = errorList;
    }
}

public sealed class ValidationErrorResultException : Exception
{
    public ValidationErrorResultException() : base("ValidationErrorResult cannot be created from a successful validation")
    {
    }
}

MediatR

Here is a gist of a Behavior that plays well with the MediatR library.

It will catch any unhandled exception inside a request handler of type IRequestHandler<TIn, IResult> or IRequestHandler<TIn, IResult< TOut >> and will guaranty that it doesn't throw exceptions.

// 1. We create a generic error that will be returned for unhandled exceptions
[ErrorResult]
public sealed partial record InternalError;

// 2. We create a generic behavior that will capture unhandled exceptions (truly exceptional exceptions)
/// <summary>
///     By default we capture every TResponse of type IResult or IResult T inside a try-catch block
///     so we can guaranty that they always return an IResult, event on unexpected exceptions.
/// </summary>
public sealed class RequestErrorHandlerBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
    where TResponse : IResult
{
    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        try
        {
            return await next().ConfigureAwait(continueOnCapturedContext: false);
        }
#pragma warning disable CA1031 // catch a more specific exception
        catch (Exception e)
#pragma warning restore CA1031
        {
            Type responseType = typeof(TResponse);
            Type[] genericArguments = responseType.GetGenericArguments();

            // if there are no generic arguments it means TResponse is of type Result
            if (genericArguments.Length == 0)
                return (TResponse)InternalError.Create(e.Message);

            // if there are generic arguments it means TResponse is of type Result<T>
            Type genericErrorType = typeof(InternalError<>);
            Type responseErrorType = genericErrorType.MakeGenericType(genericArguments);

            // We use the constructor instead of the Create factory method for convenience
            object error = Activator.CreateInstance(responseErrorType, e.Message, Array.Empty<ErrorResultDetail>())!;
            return (TResponse)error;
        }
    }
}

// 3. We register the behavior in the DI container (remember that order matters when you register this behavior)
services
    .AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestErrorHandlerBehavior<,>));

results's People

Contributors

wtorricos avatar

Stargazers

Nıkıta avatar  avatar Sebastian Steinau avatar Milton Ballon avatar

Watchers

 avatar Milton Ballon avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.