A simple, fluent discriminated union of an error or a result.
A simple, fluent discriminated union of an error or a result.
ErrorOr instance
Then, FailIf, Else, Switch, Match)Result.Success, ..)ErrorOr Loving it? Show your support by giving this project a star!
ErrorOr<T>This
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
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); // 4Or, using Then/Else and Switch/Match, you can do this
Divide(4, 2)
.Then(val => val * 2)
.SwitchFirst(
onValue: Console.WriteLine, // 4
onFirstError: error => Console.WriteLine(error.Description));Internally, the ErrorOr object has a list of Errors, so if you have multiple errors, you don't need to compromise and have only the first one.
…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.
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());……ErrorOr instanceThere are implicit converters from TResult, Error, List<Error> to ErrorOr<TResult>
ErrorOr<int> result = 5;
ErrorOr<int> result = Error.Unexpected();
ErrorOr<int> result = [Error.Validation(), Error.Validation()];public ErrorOr<int> IntToErrorOr()
{
return 5;
}public ErrorOr<int> SingleErrorToErrorOr()
{
return Error.Unexpected();
}public ErrorOr<int> MultipleErrorsToErrorOr()
{
return [
Error.Validation(description: "Invalid Name"),
Error.Validation(description: "Invalid Last Name")
];
}ErrorOrFactoryErrorOr<int> result = ErrorOrFactory.From(5);
ErrorOr<int> result = ErrorOrFactory.From<int>(Error.Unexpected());
ErrorOr<int> result = ErrorOrFactory.From<int>([Error.Validation(), Error.Validation()]);ErrorOr<int> result = await ErrorOrFactory.FromAsync(5);
ErrorOr<int> result = await ErrorOrFactory.FromAsync<int>(Error.Unexpected());
ErrorOr<int> result = await ErrorOrFactory.FromAsync<int>([Error.Validation(), Error.Validation()]);public ErrorOr<int> GetValue()
{
return ErrorOrFactory.From(5);
}public ErrorOr<int> SingleErrorToErrorOr()
{
return ErrorOrFactory.From<int>(Error.Unexpected());
}public ErrorOr<int> MultipleErrorsToErrorOr()
{
return ErrorOrFactory.From([
Error.Validation(description: "Invalid Name"),
Error.Validation(description: "Invalid Last Name")
]);
}public async Tak<ErrorOr<int>> GetValueAsync()
{
return await ErrorOrFactory.FromAsync(5);
}public async Tak<ErrorOr<int>> SingleErrorToErrorOrAsync()
{
return await ErrorOrFactory.FromAsync<int>(Error.Unexpected());
}public async Tak<ErrorOr<int>> MultipleErrorsToErrorOrAsync()
{
return await ErrorOrFactory.FromAsync([
Error.Validation(description: "Invalid Name"),
Error.Validation(description: "Invalid Last Name")
]);
}ToErrorOr Extension MethodErrorOr<int> result = 5.ToErrorOr();
ErrorOr<int> result = await Task.FromResult(5).ToErrorOrAsync();ErrorOr<int> result = Error.Unexpected().ToErrorOr<int>();
ErrorOr<int> result = new[] { Error.Unauthorized(), Error.Validation() }.ToErrorOr<int>();Task<Error> errorTask = Task.FromResult(Error.Validation());
ErrorOr<int> result = errorTask.ToErrorOrAsync<int>();List<Error> errors = [Error.Unauthorized(), Error.Validation()];
Task<List<Error>> errorsTask = Task.FromResult(errors);
ErrorOr<int> result = await errorsTask.ToErrorOrAsync<int>();IsErrorErrorOr<int> result = User.Create();
if (result.IsError)
{
// the result contains one or more errors
}IsSuccessErrorOr<int> result = User.Create();
if (result.IsSuccess)
{
// the result contains no errors
}ValueErrorOr<int> result = User.Create();
if (!result.IsError) // the result contains a value
{
Console.WriteLine(result.Value);
}ErrorsErrorOr<int> result = User.Create();
if (result.IsError)
{
result.Errors // contains the list of errors that occurred
.ForEach(error => Console.WriteLine(error.Description));
}FirstErrorErrorOr<int> result = User.Create();
if (result.IsError)
{
var firstError = result.FirstError; // only the first error that occurred
Console.WriteLine(firstError == result.Errors[0]); // true
}ErrorsOrEmptyListErrorOr<int> result = User.Create();
if (result.IsError)
{
result.ErrorsOrEmptyList // List<Error> { /* one or more errors */ }
return;
}
result.ErrorsOrEmptyList // List<Error> { }MatchThe 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.
Matchstring foo = result.Match(
value => value,
errors => $"{errors.Count} errors occurred.");MatchAsyncstring foo = await result.MatchAsync(
value => Task.FromResult(value),
errors => Task.FromResult($"{errors.Count} errors occurred."));MatchFirstThe 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.
string foo = result.MatchFirst(
value => value,
firstError => firstError.Description);MatchFirstAsyncstring foo = await result.MatchFirstAsync(
value => Task.FromResult(value),
firstError => Task.FromResult(firstError.Description));SwitchThe 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.
Switchresult.Switch(
value => Console.WriteLine(value),
errors => Console.WriteLine($"{errors.Count} errors occurred."));SwitchAsyncawait result.SwitchAsync(
value => { Console.WriteLine(value); return Task.CompletedTask; },
errors => { Console.WriteLine($"{errors.Count} errors occurred."); return Task.CompletedTask; });SwitchFirstThe 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.
result.SwitchFirst(
value => Console.WriteLine(value),
firstError => Console.WriteLine(firstError.Description));SwitchFirstAsyncawait result.SwitchFirstAsync(
value => { Console.WriteLine(value); return Task.CompletedTask; },
firstError => { Console.WriteLine(firstError.Description); return Task.CompletedTask; });ThenThenThen receives a function, and invokes it only if the result is not an error.
ErrorOr<int> foo = result
.Then(val => val * 2);Multiple Then methods can be chained together.
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.
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 invokedThenAsyncThenAsync receives an asynchronous function, and invokes it only if the result is not an error.
ErrorOr<string> foo = await result
.ThenAsync(val => DoSomethingAsync(val))
.ThenAsync(val => DoSomethingElseAsync($"The result is {val}"));ThenDo and ThenDoAsyncThenDo and ThenDoAsync
No open issues yet, or sync has not completed.