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" />
paket add FunctionalConcepts --version 1.0.0
#r "nuget: FunctionalConcepts, 1.0.0"
// 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>
Fluent discriminated union of an result pattern.
dotnet add package FunctionalConcepts
</div>
- Give it a star ⭐!
- Getting Started 🏃
- Creating an
Result
instance - 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. |
-
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.