base on A simple, fluent discriminated union of an error or a result. <div align="center"> <img src="assets/icon.png" alt="drawing" width="700px"/></br> [![NuGet](https://img.shields.io/nuget/v/erroror.svg)](https://www.nuget.org/packages/erroror) [![Build](https://github.com/amantinband/error-or/actions/workflows/build.yml/badge.svg)](https://github.com/amantinband/error-or/actions/workflows/build.yml) [![publish ErrorOr to nuget](https://github.com/amantinband/error-or/actions/workflows/publish.yml/badge.svg)](https://github.com/amantinband/error-or/actions/workflows/publish.yml) [![GitHub contributors](https://img.shields.io/github/contributors/amantinband/error-or)](https://GitHub.com/amantinband/error-or/graphs/contributors/) [![GitHub Stars](https://img.shields.io/github/stars/amantinband/error-or.svg)](https://github.com/amantinband/error-or/stargazers) [![GitHub license](https://img.shields.io/github/license/amantinband/error-or)](https://github.com/amantinband/error-or/blob/main/LICENSE) [![codecov](https://codecov.io/gh/amantinband/error-or/branch/main/graph/badge.svg?token=DR2EBIWK7B)](https://codecov.io/gh/amantinband/error-or) --- ### A simple, fluent discriminated union of an error or a result. `dotnet add package ErrorOr` </div> - [Give it a star ⭐!](#give-it-a-star-) - [Getting Started 🏃](#getting-started-) - [Replace throwing exceptions with `ErrorOr<T>`](#replace-throwing-exceptions-with-errorort) - [Support For Multiple Errors](#support-for-multiple-errors) - [Various Functional Methods and Extension Methods](#various-functional-methods-and-extension-methods) - [Real world example](#real-world-example) - [Simple Example with intermediate steps](#simple-example-with-intermediate-steps) - [No Failure](#no-failure) - [Failure](#failure) - [Creating an `ErrorOr` instance](#creating-an-erroror-instance) - [Using implicit conversion](#using-implicit-conversion) - [Using The `ErrorOrFactory`](#using-the-errororfactory) - [Using The `ToErrorOr` Extension Method](#using-the-toerroror-extension-method) - [Properties](#properties) - [`IsError`](#iserror) - [`Value`](#value) - [`Errors`](#errors) - [`FirstError`](#firsterror) - [`ErrorsOrEmptyList`](#errorsoremptylist) - [Methods](#methods) - [`Match`](#match) - [`Match`](#match-1) - [`MatchAsync`](#matchasync) - [`MatchFirst`](#matchfirst) - [`MatchFirstAsync`](#matchfirstasync) - [`Switch`](#switch) - [`Switch`](#switch-1) - [`SwitchAsync`](#switchasync) - [`SwitchFirst`](#switchfirst) - [`SwitchFirstAsync`](#switchfirstasync) - [`Then`](#then) - [`Then`](#then-1) - [`ThenAsync`](#thenasync) - [`ThenDo` and `ThenDoAsync`](#thendo-and-thendoasync) - [Mixing `Then`, `ThenDo`, `ThenAsync`, `ThenDoAsync`](#mixing-then-thendo-thenasync-thendoasync) - [`FailIf`](#failif) - [`Else`](#else) - [`Else`](#else-1) - [`ElseAsync`](#elseasync) - [Mixing Features (`Then`, `FailIf`, `Else`, `Switch`, `Match`)](#mixing-features-then-failif-else-switch-match) - [Error Types](#error-types) - [Built in error types](#built-in-error-types) - [Custom error types](#custom-error-types) - [Built in result types (`Result.Success`, ..)](#built-in-result-types-resultsuccess-) - [Organizing Errors](#organizing-errors) - [Mediator + FluentValidation + `ErrorOr` 🤝](#mediator--fluentvalidation--erroror-) - [Contribution 🤲](#contribution-) - [Credits 🙏](#credits-) - [License 🪪](#license-) # Give it a star ⭐! Loving it? Show your support by giving this project a star! # Getting Started 🏃 ## Replace throwing exceptions with `ErrorOr<T>` This 👇 ```cs public float Divide(int a, int b) { if (b == 0) { throw new Exception("Cannot divide by zero"); } return a / b; } try { var result = Divide(4, 2); Console.WriteLine(result * 2); // 4 } catch (Exception e) { Console.WriteLine(e.Message); return; } ``` Turns into this 👇 ```cs public ErrorOr<float> Divide(int a, int b) { if (b == 0) { return Error.Unexpected(description: "Cannot divide by zero"); } return a / b; } var result = Divide(4, 2); if (result.IsError) { Console.WriteLine(result.FirstError.Description); return; } Console.WriteLine(result.Value * 2); // 4 ``` Or, using [Then](#then--thenasync)/[Else](#else--elseasync) and [Switch](#switch--switchasync)/[Match](#match--matchasync), you can do this 👇 ```cs Divide(4, 2) .Then(val => val * 2) .SwitchFirst( onValue: Console.WriteLine, // 4 onFirstError: error => Console.WriteLine(error.Description)); ``` ## Support For Multiple Errors Internally, the `ErrorOr` object has a list of `Error`s, so if you have multiple errors, you don't need to compromise and have only the first one. ```cs public class User(string _name) { public static ErrorOr<User> Create(string name) { List<Error> errors = []; if (name.Length < 2) { errors.Add(Error.Validation(description: "Name is too short")); } if (name.Length > 100) { errors.Add(Error.Validation(description: "Name is too long")); } if (string.IsNullOrWhiteSpace(name)) { errors.Add(Error.Validation(description: "Name cannot be empty or whitespace only")); } if (errors.Count > 0) { return errors; } return new User(name); } } ``` ## Various Functional Methods and Extension Methods The `ErrorOr` object has a variety of methods that allow you to work with it in a functional way. This allows you to chain methods together, and handle the result in a clean and concise way. ### Real world example ```cs return await _userRepository.GetByIdAsync(id) .Then(user => user.IncrementAge() .Then(success => user) .Else(errors => Error.Unexpected("Not expected to fail"))) .FailIf(user => !user.IsOverAge(18), UserErrors.UnderAge) .ThenDo(user => _logger.LogInformation($"User {user.Id} incremented age to {user.Age}")) .ThenAsync(user => _userRepository.UpdateAsync(user)) .Match( _ => NoContent(), errors => errors.ToActionResult()); ``` ### Simple Example with intermediate steps #### No Failure ```cs ErrorOr<string> foo = await "2".ToErrorOr() .Then(int.Parse) // 2 .FailIf(val => val > 2, Error.Validation(description: $"{val} is too big") // 2 .ThenDoAsync(Task.Delay) // Sleep for 2 milliseconds .ThenDo(val => Console.WriteLine($"Finished waiting {val} milliseconds.")) // Finished waiting 2 milliseconds. .ThenAsync(val => Task.FromResult(val * 2)) // 4 .Then(val => $"The result is {val}") // "The result is 4" .Else(errors => Error.Unexpected(description: "Yikes")) // "The result is 4" .MatchFirst( value => value, // "The result is 4" firstError => $"An error occurred: {firstError.Description}"); ``` #### Failure ```cs ErrorOr<string> foo = await "5".ToErrorOr() .Then(int.Parse) // 5 .FailIf(val => val > 2, Error.Validation(description: $"{val} is too big") // Error.Validation() .ThenDoAsync(Task.Delay) // Error.Validation() .ThenDo(val => Console.WriteLine($"Finished waiting {val} milliseconds.")) // Error.Validation() .ThenAsync(val => Task.FromResult(val * 2)) // Error.Validation() .Then(val => $"The result is {val}") // Error.Validation() .Else(errors => Error.Unexpected(description: "Yikes")) // Error.Unexpected() .MatchFirst( value => value, firstError => $"An error occurred: {firstError.Description}"); // An error occurred: Yikes ``` # Creating an `ErrorOr` instance ## Using implicit conversion There are implicit converters from `TResult`, `Error`, `List<Error>` to `ErrorOr<TResult>` ```cs ErrorOr<int> result = 5; ErrorOr<int> result = Error.Unexpected(); ErrorOr<int> result = [Error.Validation(), Error.Validation()]; ``` ```cs public ErrorOr<int> IntToErrorOr() { return 5; } ``` ```cs public ErrorOr<int> SingleErrorToErrorOr() { return Error.Unexpected(); } ``` ```cs public ErrorOr<int> MultipleErrorsToErrorOr() { return [ Error.Validation(description: "Invalid Name"), Error.Validation(description: "Invalid Last Name") ]; } ``` ## Using The `ErrorOrFactory` ```cs ErrorOr<int> result = ErrorOrFactory.From(5); ErrorOr<int> result = ErrorOrFactory.From<int>(Error.Unexpected()); ErrorOr<int> result = ErrorOrFactory.From<int>([Error.Validation(), Error.Validation()]); ``` ```cs public ErrorOr<int> GetValue() { return ErrorOrFactory.From(5); } ``` ```cs public ErrorOr<int> SingleErrorToErrorOr() { return ErrorOrFactory.From<int>(Error.Unexpected()); } ``` ```cs public ErrorOr<int> MultipleErrorsToErrorOr() { return ErrorOrFactory.From([ Error.Validation(description: "Invalid Name"), Error.Validation(description: "Invalid Last Name") ]); } ``` ## Using The `ToErrorOr` Extension Method ```cs ErrorOr<int> result = 5.ToErrorOr(); ErrorOr<int> result = Error.Unexpected().ToErrorOr<int>(); ErrorOr<int> result = new[] { Error.Validation(), Error.Validation() }.ToErrorOr<int>(); ``` # Properties ## `IsError` ```cs ErrorOr<int> result = User.Create(); if (result.IsError) { // the result contains one or more errors } ``` ## `Value` ```cs ErrorOr<int> result = User.Create(); if (!result.IsError) // the result contains a value { Console.WriteLine(result.Value); } ``` ## `Errors` ```cs ErrorOr<int> result = User.Create(); if (result.IsError) { result.Errors // contains the list of errors that occurred .ForEach(error => Console.WriteLine(error.Description)); } ``` ## `FirstError` ```cs ErrorOr<int> result = User.Create(); if (result.IsError) { var firstError = result.FirstError; // only the first error that occurred Console.WriteLine(firstError == result.Errors[0]); // true } ``` ## `ErrorsOrEmptyList` ```cs ErrorOr<int> result = User.Create(); if (result.IsError) { result.ErrorsOrEmptyList // List<Error> { /* one or more errors */ } return; } result.ErrorsOrEmptyList // List<Error> { } ``` # Methods ## `Match` The `Match` method receives two functions, `onValue` and `onError`, `onValue` will be invoked if the result is success, and `onError` is invoked if the result is an error. ### `Match` ```cs string foo = result.Match( value => value, errors => $"{errors.Count} errors occurred."); ``` ### `MatchAsync` ```cs string foo = await result.MatchAsync( value => Task.FromResult(value), errors => Task.FromResult($"{errors.Count} errors occurred.")); ``` ### `MatchFirst` The `MatchFirst` method receives two functions, `onValue` and `onError`, `onValue` will be invoked if the result is success, and `onError` is invoked if the result is an error. Unlike `Match`, if the state is error, `MatchFirst`'s `onError` function receives only the first error that occurred, not the entire list of errors. ```cs string foo = result.MatchFirst( value => value, firstError => firstError.Description); ``` ### `MatchFirstAsync` ```cs string foo = await result.MatchFirstAsync( value => Task.FromResult(value), firstError => Task.FromResult(firstError.Description)); ``` ## `Switch` The `Switch` method receives two actions, `onValue` and `onError`, `onValue` will be invoked if the result is success, and `onError` is invoked if the result is an error. ### `Switch` ```cs result.Switch( value => Console.WriteLine(value), errors => Console.WriteLine($"{errors.Count} errors occurred.")); ``` ### `SwitchAsync` ```cs await result.SwitchAsync( value => { Console.WriteLine(value); return Task.CompletedTask; }, errors => { Console.WriteLine($"{errors.Count} errors occurred."); return Task.CompletedTask; }); ``` ### `SwitchFirst` The `SwitchFirst` method receives two actions, `onValue` and `onError`, `onValue` will be invoked if the result is success, and `onError` is invoked if the result is an error. Unlike `Switch`, if the state is error, `SwitchFirst`'s `onError` function receives only the first error that occurred, not the entire list of errors. ```cs result.SwitchFirst( value => Console.WriteLine(value), firstError => Console.WriteLine(firstError.Description)); ``` ### `SwitchFirstAsync` ```cs await result.SwitchFirstAsync( value => { Console.WriteLine(value); return Task.CompletedTask; }, firstError => { Console.WriteLine(firstError.Description); return Task.CompletedTask; }); ``` ## `Then` ### `Then` `Then` receives a function, and invokes it only if the result is not an error. ```cs ErrorOr<int> foo = result .Then(val => val * 2); ``` Multiple `Then` methods can be chained together. ```cs ErrorOr<string> foo = result .Then(val => val * 2) .Then(val => $"The result is {val}"); ``` If any of the methods return an error, the chain will break and the errors will be returned. ```cs ErrorOr<int> Foo() => Error.Unexpected(); ErrorOr<string> foo = result .Then(val => val * 2) .Then(_ => GetAnError()) .Then(val => $"The result is {val}") // this function will not be invoked .Then(val => $"The result is {val}"); // this function will not be invoked ``` ### `ThenAsync` `ThenAsync` receives an asynchronous function, and invokes it only if the result is not an error. ```cs ErrorOr<string> foo = await result .ThenAsync(val => DoSomethingAsync(val)) .ThenAsync(val => DoSomethingElseAsync($"The result is {val}")); ``` ### `ThenDo` and `ThenDoAsync` `ThenDo` and `ThenDoAsync` are similar to `Then` and `ThenAsync`, but instead of invoking a function that returns a value, they invoke an action. ```cs ErrorOr<string> foo = result .ThenDo(val => Console.WriteLine(val)) .ThenDo(val => Console.WriteLine($"The result is {val}")); ``` ```cs ErrorOr<string> foo = await result .ThenDoAsync(val => Task.Delay(val)) .ThenDo(val => Console.WriteLine($"Finsihed waiting {val} seconds.")) .ThenDoAsync(val => Task.FromResult(val * 2)) .ThenDo(val => $"The result is {val}"); ``` ### Mixing `Then`, `ThenDo`, `ThenAsync`, `ThenDoAsync` You can mix and match `Then`, `ThenDo`, `ThenAsync`, `ThenDoAsync` methods. ```cs ErrorOr<string> foo = await result .ThenDoAsync(val => Task.Delay(val)) .Then(val => val * 2) .ThenAsync(val => DoSomethingAsync(val)) .ThenDo(val => Console.WriteLine($"Finsihed waiting {val} seconds.")) .ThenAsync(val => Task.FromResult(val * 2)) .Then(val => $"The result is {val}"); ``` ## `FailIf` `FailIf` receives a predicate and an error. If the predicate is true, `FailIf` will return the error. Otherwise, it will return the value of the result. ```cs ErrorOr<int> foo = result .FailIf(val => val > 2, Error.Validation(description: $"{val} is too big")); ``` Once an error is returned, the chain will break and the error will be returned. ```cs var result = "2".ToErrorOr() .Then(int.Parse) // 2 .FailIf(val => val > 1, Error.Validation(description: $"{val} is too big") // validation error .Then(num => num * 2) // this function will not be invoked .Then(num => num * 2) // this function will not be invoked ``` ## `Else` `Else` receives a value or a function. If the result is an error, `Else` will return the value or invoke the function. Otherwise, it will return the value of the result. ### `Else` ```cs ErrorOr<string> foo = result .Else("fallback value"); ``` ```cs ErrorOr<string> foo = result .Else(errors => $"{errors.Count} errors occurred."); ``` ### `ElseAsync` ```cs ErrorOr<string> foo = await result .ElseAsync(Task.FromResult("fallback value")); ``` ```cs ErrorOr<string> foo = await result .ElseAsync(errors => Task.FromResult($"{errors.Count} errors occurred.")); ``` # Mixing Features (`Then`, `FailIf`, `Else`, `Switch`, `Match`) You can mix `Then`, `FailIf`, `Else`, `Switch` and `Match` methods together. ```cs ErrorOr<string> foo = await result .ThenDoAsync(val => Task.Delay(val)) .FailIf(val => val > 2, Error.Validation(description: $"{val} is too big")) .ThenDo(val => Console.WriteLine($"Finished waiting {val} seconds.")) .ThenAsync(val => Task.FromResult(val * 2)) .Then(val => $"The result is {val}") .Else(errors => Error.Unexpected()) .MatchFirst( value => value, firstError => $"An error occurred: {firstError.Description}"); ``` # Error Types Each `Error` instance has a `Type` property, which is an enum value that represents the type of the error. ## Built in error types The following error types are built in: ```cs public enum ErrorType { Failure, Unexpected, Validation, Conflict, NotFound, Unauthorized, Forbidden, } ``` Each error type has a static method that creates an error of that type. For example: ```cs var error = Error.NotFound(); ``` optionally, you can pass a code, description and metadata to the error: ```cs 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 categorize errors. ## Custom error types You can create your own error types if you would like to categorize your errors differently. A custom error type can be created with the `Custom` static method ```cs public static class MyErrorTypes { const int ShouldNeverHappen = 12; } var error = Error.Custom( type: MyErrorTypes.ShouldNeverHappen, code: "User.ShouldNeverHappen", description: "A user error that should never happen"); ``` You can use the `Error.NumericType` method to retrieve the numeric type of the error. ```cs var errorMessage = Error.NumericType switch { MyErrorType.ShouldNeverHappen => "Consider replacing dev team", _ => "An unknown error occurred.", }; ``` # Built in result types (`Result.Success`, ..) There are a few built in result types: ```cs ErrorOr<Success> result = Result.Success; ErrorOr<Created> result = Result.Created; ErrorOr<Updated> result = Result.Updated; ErrorOr<Deleted> result = Result.Deleted; ``` Which can be used as following ```cs ErrorOr<Deleted> DeleteUser(Guid id) { var user = await _userRepository.GetByIdAsync(id); if (user is null) { return Error.NotFound(description: "User not found."); } await _userRepository.DeleteAsync(user); return Result.Deleted; } ``` # Organizing Errors A nice approach, is creating a static class with the expected errors. For example: ```cs public static partial class DivisionErrors { public static Error CannotDivideByZero = Error.Unexpected( code: "Division.CannotDivideByZero", description: "Cannot divide by zero."); } ``` Which can later be used as following 👇 ```cs public ErrorOr<float> Divide(int a, int b) { if (b == 0) { return DivisionErrors.CannotDivideByZero; } return a / b; } ``` # [Mediator](https://github.com/jbogard/MediatR) + [FluentValidation](https://github.com/FluentValidation/FluentValidation) + `ErrorOr` 🤝 A common approach when using `MediatR` is to use `FluentValidation` to validate the request before it reaches the handler. Usually, the validation is done using a `Behavior` that throws an exception if the request is invalid. Using `ErrorOr`, we can create a `Behavior` that returns an error instead of throwing an exception. This plays nicely when the project uses `ErrorOr`, as the layer invoking the `Mediator`, similar to other components in the project, simply receives an `ErrorOr` and can handle it accordingly. Here is an example of a `Behavior` that validates the request and returns an error if it's invalid 👇 ```cs public class ValidationBehavior<TRequest, TResponse>(IValidator<TRequest>? validator = null) : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TResponse : IErrorOr { private readonly IValidator<TRequest>? _validator = validator; public async Task<TResponse> Handle( TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { if (_validator is null) { return await next(); } var validationResult = await _validator.ValidateAsync(request, cancellationToken); if (validationResult.IsValid) { return await next(); } var errors = validationResult.Errors .ConvertAll(error => Error.Validation( code: error.PropertyName, description: error.ErrorMessage)); return (dynamic)errors; } } ``` # Contribution 🤲 If you have any questions, comments, or suggestions, please open an issue or create a pull request 🙂 # Credits 🙏 - [OneOf](https://github.com/mcintyre321/OneOf/tree/master/OneOf) - An awesome library which provides F# style discriminated unions behavior for C# # License 🪪 This project is licensed under the terms of the [MIT](https://github.com/mantinband/error-or/blob/main/LICENSE) license. ", Assign "at most 3 tags" to the expected json: {"id":"9218","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"