Giter Site home page Giter Site logo

functionalconcepts's Introduction

NuGet

Build publish FunctionalConcepts to nuget

GitHub contributors GitHub Stars GitHub license codecov


Fluente Discriminated Union para padrão Result.

dotnet add package FunctionalConcepts

Dê uma estrela ⭐!

Gostou? Mostre seu apoio dando uma estrela :D

Começando 🏃

Temos 3 tipos de união discriminada: Result<T>, Choice<TLeft, TRight> e Option<T>. Escolha qual utilizar. A documentação ainda está em desenvolvimento para maior clareza.

Substitua Throw por retorno de Result<T>

Isso aqui 👇

public float Operation(int num1, int num2)
{
    if (num2 == 0)
    {
        throw new Exception("Impossível dividir por zero");
    }

    return num1 / num2;
}

try
{
    var result = Operation(4, 2);
    Console.WriteLine(result * 3);
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
    return;
}

Se torna isso 👇

public Result<float> Operation(int a, int b)
{
    if (b == 0)
    {
        return (Code: 500, Message: "Impossível dividir por zero");
    }

    return a / b;
}

var result = Operation(4, 2);

var msg = result.Match(
    val => $"{(val * 3)}",
    fail => $"código: {fail.Code} msg: {fail.Message}");

Console.WriteLine(msg);

Métodos

O objeto Result possui alguns métodos que permitem um trabalho funcional, por serem métodos puros, não modificam o estado do resultado atual.

Exemplo real

Um exemplo real de manipulação, onde a busca na camada de repositório é feita e retorna uma opção. Caso essa opção tenha um valor válido, o livro é excluído; caso contrário, retorna uma mensagem de erro NotFound para a camada superior, que neste caso é uma API.

public async Task<Result<Success>> Handle(
    BookRemoveCommand cmd,
    CancellationToken cancellationToken)
{

    var maybeBook = await _repositoryBook_.Get(cmd.Id);

    return await maybeBook.MatchAsync(
        async book => Result.Of(await _repositoryBook_.Delete(book, cancellationToken)),
        () => (NotFoundError)$"Livro com id: {cmd.Id} não encontrado");
}

Criando um Result

Conversão implícita

Existem conversores implícitos de TSuccess ou de BaseError para Result. Por exemplo:

Result<Success> resultado = Result.Success; // pode ser feito com resultado = default(Success)

Success é uma estrutura vazia para representação de retorno, substituindo o "void". Será abordado no futuro.

Aqui está um exemplo com classe complexa:

string msg = "mensagem de teste";
ExemploTeste teste = new ExemploTeste(msg);
Result<ExemploTeste> resultado = teste;

Pode ser feito como retorno em um método também, caso necessário:

public Result<int> ResultAsInt()
{
    return 5;
}

Aqui está um exemplo de resultado que será lido como erro. É possível fazer uma conversão de tupla para o objeto result e passar seus valores para Código e Mensagem:

public Result<int> ResultAsErro()
{
    return (404, "objeto não encontrado");
}

Caso precise propagar uma exceção com o result, também é possível adicioná-lo à tupla, evitando assim a propagação de exceção e dando mais controle sobre as falhas. Como no exemplo abaixo:

public Result<float> Operation(int a, int b)
{
    try {
       return a / b;
    }
    catch(Exception ex)
    {
        return (Code: 500, Message: "Impossível dividir por zero", Exception: ex);
        // também é possível retornar apenas: (500, "Impossível dividir por zero", ex)
    }
}

Utilizando o Factory

Result

Em algumas situações, como interfaces por exemplo, a conversão implicita não é possivel, dessa forma é possivel a criação por um metodo especifico, o Of, abaixo o exemplo.

IQueryable<int> query = new List<int> { 1, 2, 3}.AsQueryable();
//cria um result de sucesso
Result<IQueryable<int>> result = Result.Of(query);

//cria um result de falha
Result<int> result = Result.Of<IQueryable<int>>(404, "object not found");

Também é possivel utilizar com tipos comuns.

Result<int> result = Result.Of(5);//cria um result de sucesso
Result<int> result = Result.Of<int>(404, "object not found");//cria um result de falha

E em retorno de metodos.

public Result<int> GetValue()
{
    return Result.Of(5);
}

Quando um cenario de falha, deve ser expecificado o tipo entre <> pois do contrario o tipo ficaria Resul, redundante, pois Result por si ja assume o papel de Erro e deve ser especificado a o sucesso entre <>

public Result<int> ErrorAsResult()
{
    return Result.Of<int>((ConflictError)"Mensagem de conflito");
}

Erros

Caso queira, é possivel a inicialização de novas instancias de erros com o BaseError.New()

BaseError error = BaseError.New(404, "404 em base error");

Options

Em options segue a mesma ideia do result.

var opt = Option.Of<int>(16); // opt é do tipo Option<int>

Propriedades

Caso o resultado seja uma falha, existe uma propriedade boolean que indica essa condição, caso queira utilizar.

Result

IsFail

int userId = 19;
Result<int> result = userId;

if (result.IsFail)
{
    // se result for erro, esse trecho será executado
}

Error´

Não é possivel acessar o erro ou o valor diretamente, para isso é preciso utilizar de alguns metodos existentes dentro da biblioteca para acessalos de maneira segura. Dessa forma, evitamos ifs de comparação de nulavel dentro do codigo e garatimos o fluxo correto de acordo com seus valores, segue exemplos abaixo.

Metodos.

Then

Método que permite seguir um fluxo mais fluente com base no resultado em caso de situação de sucesso.

Result<int> foo = result.Then(v => v + 5);

Também é possível aumentar a fluência, ou seja, adicionar mais operadores para seguir o fluxo. Claro, se algum dos cenários retornar um erro ou se o resultado já for um erro, o fluxo não executará a função passada.

Result<string> foo = result
    .Then(val => val + 5)
    .Then(val => val + 2)

Else metodo para o fluxo em caso de falha

Result<Company> result = Company.GetFirst();

result.Else(fail => {
     Console.WriteLine(fail.Message)
});

Match e MatchAsync

O Match recebe duas funções como parametros, onSome and onError, onSome é executado quando Result for um Sucesso, do contrario é executada a função passada em onError.

Match

string foo = result.Match(
    some => some,
    error => $"Msg: {error.Message}");
//Em caso de sucess, Foo assume o valor da mensagem dentro de result, em caso de falha Foo fica com valor "Msg: mensagem"

Async no Match

Mesma coisa que Match normal, contudo, aceita funções que retornam Task para executar.

string foo = await result.MatchAsync(
    async some => await Task.FromResult(some),
    async error => await Task.FromResult($"Msg: {error.Message}"));

Error Types

Built in error types

Temos um enum de erros. No entanto, esse enum é apenas utilizado para indicar quais erros cada classe de erro retorna. A classe base BaseError recebe um inteiro para possibilitar erros personalizados por cada programa.

public enum EErrorCode
{
    Unauthorized = 401,
    Forbidden = 0403,
    NotFound = 0404,
    Conflict = 0409,
    NotAllowed = 0405,
    InvalidObject = 0422,
    Unhandled = 0500,
    ServiceUnavailable = 0503,
}

Tipos de erros e como inicializalos

Primeiro, a classe base permite a criação de erros além dos já existentes.

private Result<int> FuncReturnError(){
     return (501, "erro com codigo 501")
}

Result<int> result = 1;

Result<string> foo = result
    .Then(val => val + 5)
    .Then(_ => FuncReturnError())
    .Then(v => Console.WriteLine($"value is: {v}"))

Nota: Na última situação, onde o valor seria mostrado no console, não será executada porque FuncReturnError retorna um tipo de erro.

Erros são criados implicitamente com base em uma tupla informada entre parênteses. Também é possível propagar uma exceção em casos que sejam necessários.

private Result<int> FuncReturnError()
{
     try
     {
         //algum erro acontece aqui
     }
     catch(Exception exn)
     {
           return (501, "erro com codigo 501", exn)
     }
}

Os seguintes erros já estão presentes, com seus respectivos códigos de erro. É importante lembrar que tudo é feito implicitamente; ou seja, apenas crie a mensagem que estará presente no erro.

ConflictError conflict = "mensagem de conflict";
ForbiddenError forbidden = "mensagem de forbidden";
InvalidObjectError invalidObj = "mensagem de invalidObj";
NotAllowedError notAllowed = "mensagem de notAllowed";
NotFoundError norFound = "mensagem de norFound";
ServiceUnavailableError unavailable = "mensagem de unavailable";
UnauthorizedError unauth = "mensagem de unauth";
UnhandledError unhandled = "mensagem de unhandled";

Também é possível passar Exception para um erro padrão, contudo, será necessário inicializá-lo como uma tupla.

ConflictError conflict = ("mensagem de conflict", exn);

E claro, para acessar a mensagem ou o código de erro, basta acessar as propriedades correspondentes.

conflict.Code;
conflict.Message;
conflict.Exception;

Mediator + FluentValidation + FunctionalConcepts 🤝

Quando se utiliza MediatR, é bastante comum usar FluentValidation para validar o request. As validações ocorrem com Behavior que lançam exceções se o request estiver inválido.

Usando conceitos funcionais com Result, criamos um Behavior que retorna um erro em vez de lançar uma exceção.

Um exemplo de Behavior: 👇

public class ValidatorBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, Result<TResponse>>
    where TRequest : notnull
{
    private readonly IValidator<TRequest>[] _validators;

    public ValidatorBehavior(IValidator<TRequest>[] validators) => _validators = validators;

    public async Task<Result<TResponse>> Handle(TRequest request, RequestHandlerDelegate<Result<TResponse>> next, CancellationToken cancellationToken)
    {
        List<FluentValidation.Results.ValidationFailure> failures = _validators
            .Select(v => v.Validate(request))
            .SelectMany(result => result.Errors)
            .Where(error => error != null)
            .ToList();

        return failures.Any()
               ? (InvalidObjectError)("Erro ao executar validations", new ValidationException(failures))
               : await next();
    }
}

Contribution 🤲

Se tiver alguma pergunta, comentário ou sugestão, por favor, abra uma issue ou crie um pull request.🙂

Creditos🙏

  • LanguageExt - Library with complexy approch arround results and functional programming in C#
  • ErrorOr - Simple way to functional with errors, amazing library.
  • OneOf - Provides F# style discriminated unions behavior for C#

License 🪪

Licensed under the terms of GNU General Public License v3.0 license.

functionalconcepts's People

Contributors

aleffmoura avatar

Stargazers

 avatar

Watchers

 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.