FunctionalConcepts 1.0.0
See the version list below for details.
dotnet add package FunctionalConcepts --version 1.0.0
NuGet\Install-Package FunctionalConcepts -Version 1.0.0
<PackageReference Include="FunctionalConcepts" Version="1.0.0" />
<PackageVersion Include="FunctionalConcepts" Version="1.0.0" />
<PackageReference Include="FunctionalConcepts" />
paket add FunctionalConcepts --version 1.0.0
#r "nuget: FunctionalConcepts, 1.0.0"
#:package FunctionalConcepts@1.0.0
#addin nuget:?package=FunctionalConcepts&version=1.0.0
#tool nuget:?package=FunctionalConcepts&version=1.0.0
<div align="center">
<img src="assets/icon.png" alt="drawing" width="700px"/></br>
Fluent discriminated union of an result pattern.
dotnet add package FunctionalConcepts
</div>
- Give it a star ⭐!
- Getting Started 🏃
- Creating an
Resultinstance - Properties
- Methods
- Mixing Features (
IfFail,IfSuccess,Match) - Error Types
- Built in result types (
Result.Success, ..) - Organizing Errors
- Mediator + FluentValidation +
FunctionalConcepts🤝 - Contribution 🤲
- Credits 🙏
- License 🪪
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 | Versions 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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
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.