FunctionalConcepts 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package FunctionalConcepts --version 1.0.0                
NuGet\Install-Package FunctionalConcepts -Version 1.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FunctionalConcepts" Version="1.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add FunctionalConcepts --version 1.0.0                
#r "nuget: FunctionalConcepts, 1.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install FunctionalConcepts as a Cake Addin
#addin nuget:?package=FunctionalConcepts&version=1.0.0

// Install FunctionalConcepts as a Cake Tool
#tool nuget:?package=FunctionalConcepts&version=1.0.0                

<div align="center">

<img src="assets/icon.png" alt="drawing" width="700px"/></br>

NuGet

Build publish FunctionalConcepts to nuget

GitHub contributors GitHub Stars GitHub license codecov

Fluent discriminated union of an result pattern.

dotnet add package FunctionalConcepts

</div>

Dê uma estrela ⭐!

Você gostou? Mostre para nós dando uma estrela 😄

Iniciando 🏃

Troque Throw Exception por return Result<T>

Isto👇

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

    return num1 / num2;
}

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

Se torna isto 👇

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

    return a / b;
}

var result = Operation(4, 2);

result.Match(val => {
    Console.WriteLine(result.Value * 3); // 6
}, fail => {
    Console.WriteLine($"codigo: {fail.Code} msg: {fail.Message}");
})

Metodos Funcionais e de Extensão

O objecto Result possui alguns metodos que lhe permite trabalhar em uma abordagem de forma funcional, isto permite trabalhar com metodos fluentes e obter resultados mais coesos.

Exemplo real

return await _repository.GetByIdAsync(id);

Simple Example

No Failure

Failure

Criando uma instancia de Result

Conversão implicita

Existem conversores implicitos de TSuccess ou de BaseError para Result<TSuccess> Como por exemplo os treichos abaixo.

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

Success é uma struct vazia para representação de retorno substituindo o "void", será abordado o tema mais para frente.

Aqui um exemplo com classe complexa.

string msg = "test message";
ExampleTest teste = new ExampleTest(msg);
Result<ExampleTest> result = teste;

Pode ser feito como return em um metodo tambem para caso necessario.

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

Abaixo um exemplo de resultado ao qual será lido como erro, é possivel fazer uma conversão de tupla para o objeto result e passar seus valores para Code e Message

public Result<int> ErrorAsResult()
{
    return (404, "object not found");
}

Caso sinta necessacidade de propagar uma exception com o result, tambem é possivel adiciona-lo a tupla, evitando assim a propagação de exception dando mais throws; conforme exemplo abaixo.

public Result<float> Operation(int a, int b) { try { return a / b; } catch(Exception ex) { return (Code: 500, Message: "Impossivel dividir por zero", Exception: ex); //tambem é possivel retorno apenas com: (500, "Impossivel dividir por zero", ex) } }

Utilizando o Factory

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<ConflictError>, 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");
}

Using The AsResult Extension

Properties

IsFail

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

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

Error

Como a programação funcional prega, 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.

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

// Você pode utilizar IfFail para acessar em caso de falha, ou Match para acessar os dois valores.

result.IfFail(fail => {
     Console.WriteLine(fail.Message)
});
//IfFail executa apenas quando result possuir valor de falha.

Metodos.

Match

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, porem aceita funções que retornam Task para executar.

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

Then

Metodo que permite seguir um fluxo mais fluente com base no result em caso de situação de sucesso.

Result<int> foo = result.Then(v => v + 5);
//a variavel 'v' é o valor salvo dentro de result, se result for sucesso então a soma é aplicada

### `Pipe`

Pipe aceita uma função que executa aceita o objeto passado de forma fluente, assim como o Then é uma função para manter a fluencia no codigo, contudo ele não "abre o result" ele passa o proprio result pra função de parametro.

```cs
Result<int> foo = result.Pipe(v => v + 5);
//a variavel 'v' é o proprio result, por isso não é possivel somar.

Multiplos Pipe e Then metodos podem ser aplicados em conjutos

Result<string> foo = result
    .Then(val => val + 5)
    .Then(val => val + 2)
    .Map(v => $"value is: {v}");

Se algum dos metodos retornar erro, o then não da continuidade nas funções seguintes

Result<int> Foo() => Error.NotFound();

Result<string> foo = result
    .Pipe(val => val + 5)
    .Pipe(_ => FuncReturnError())
    .Pipe(v => $"value is: {v}") // will not be invoked

Error Types

Each Error instance has a Type, which is an enum that represents the type error.

Built in error types

The following error types are built in:

public enum ErrorType
{
    Failure,
    Unexpected,
    Validation,
    Conflict,
    NotFound,
    Unauthorized,
    Forbidden,
}

Each error type has a extension method that creates an error of that type:

var error = Error.NotFound();

you can pass a code as well and message to the error:

var error = Error.Unexpected(
    code: "User.ShouldNeverHappen",
    description: "A user error that should never happen",
    metadata: new Dictionary<string, object>
    {
        { "user", user },
    });

The ErrorType enum is a good way to response in WebApi RestFULL.

Custom error types

You can create your own error if necessary.

A custom error type can be created with the Create method

public static class MyErrorTypes
{
    const int ShouldNeverHappen = 12;
}

var error = Error.Create(
    type: MyErrorTypes.ShouldNeverHappen,
    code: "User.ShouldNeverHappen",
    description: "A user error that should never happen");

You can use the Error.Number method to retrieve the number representing of the erro.

var errorMessage = Error.Number switch
{
    MyErrorType.ShouldNeverHappen => "Contact the support",
    _ => "unknown.",
};

Built in result types (Result.Success, ..)

There are a few built in result types:

Result<Success> result = Result.Scs;
Result<Created> result = Result.Ctd;
Result<Updated> result = Result.Upd;
Result<Deleted> result = Result.Del;

Which can be used as following

Result<Deleted> Delete(Guid id)
{
    var entity = await _repository.GetByIdAsync(id);
    if (entity is null)
    {
        return Error.NotFound("User not found.");
    }

    await _repository.DeleteAsync(entity);
    return Result.Del;
}

Organizing Errors

Mediator + FluentValidation + FunctionalConcepts 🤝

When using MediatR is normal to use FluentValidation to validate the request.

Validation occurs with Behavior that throws an exception if the request is invalid.

Using functional concepts with Result, we create that Behavior that returns an result instead of throw an exn.

An example of a Behavior 👇


Contribution 🤲

If you have any questions, comments, or suggestions, please open an issue or create a pull request 🙂

Credits 🙏

  • 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 MIT license.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
6.0.0.4 134 8/16/2024
6.0.0.3 119 8/14/2024
6.0.0.2 109 7/20/2024
6.0.0.1 102 6/20/2024
6.0.0 100 6/20/2024
1.0.0 101 6/17/2024