error_or_plus

Last updated:

0 purchases

error_or_plus Image
error_or_plus Images
Add to Cart

Description:

error or plus

ErrorOr Plus #
This library is a porting of ErrorOr for C# made by Amichai Mantinband
A simple, fluent discriminated union of an error or a result. #
dart pub add error_or_plus


Give it a star ⭐!
Getting Started 🏃

Replace throwing exceptions with ErrorOr<T>
Support For Multiple errors
Various Functional Methods and Extension Methods

Real world example
Simple Example with intermediate steps

No Failure
Failure






Creating an ErrorOr instance

Using The ToErrorOr Extension Method


Properties

isError
value
errors
firstError
errorsOrEmptyList


Methods

match

match
matchAsync
matchFirst
matchFirstAsync


doSwitch

doSwitch
doSwitchAsync
doSwitchFirst
doSwitchFirstAsync


also

also
alsoAsync
alsoDo and alsoDoAsync
Mixing also, alsoDo, alsoAsync, alsoDoAsync


failIf
orElse

orElse
orElseAsync




Mixing Features (also, failIf, orElse, doSwitch, match)
Errors Types

Built in error types


Organizing errors
Contribution 🤲
Credits 🙏
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 👇
double divide(int a, int b)
{
if (b == 0)
{
throw Exception("Cannot divide by zero");
}

return a / b;
}

try
{
var result = divide(4, 2);
print(result * 2); // 4
}
on Exception catch (e)
{
print(e);
return;
}
copied to clipboard
Turns into this 👇
ErrorOr<double> divide(int a, int b)
{
if (b == 0)
{
return Errors.unexpected(description: "Cannot divide by zero");
}

return a / b;
}

var result = divide(4, 2);

if (result.isError)
{
print(result.firstError.description);
return;
}

print(result.value * 2); // 4
copied to clipboard
Or, using also/orElse and doSwitch/match, you can do this 👇

divide(4, 2)
.also((val) => val * 2)
.doSwitchFirst(
onValue: print, // 4
onFirstError: (error) => print(error.description));
copied to clipboard
Support For Multiple errors #
Internally, the ErrorOr object has a list of Errorss, so if you have multiple errors, you don't need to compromise and have only the first one.
class User
{
final String _name;

User._internal(this._name);

static ErrorOr<User> create(String name)
{
List<Errors> errors = [];

if (name.length < 2)
{
errors.add(Errors.validation(description: "Name is too short"));
}

if (name.length > 100)
{
errors.add(Errors.validation(description: "Name is too long"));
}

if (name.isEmpty)
{
errors.add(Errors.validation(description: "Name cannot be empty or whitespace only"));
}

if (errors.isNotEmpty)
{
return errors.toErrorOr<User>();
}

return User._internal(name).toErrorOr();
}
}
copied to clipboard
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 #
return await _userRepository.getByIdAsync(id)
.also((user) => user.incrementAge()
.also((success) => user)
.orElse(errorOnErrorHandler: (errors) => Errors.unexpected("Not expected to fail")))
.failIf((user) => !user.isOverAge(18), UserErrors.underAge)
.alsoDo((user) => _logger.logInformation("User ${user.Id} incremented age to ${user.Age}"))
.alsoAsync((user) => _userRepository.updateAsync(user))
.match(
(_) => noContent(),
(errors) => errors.toActionResult());
copied to clipboard
Simple Example with intermediate steps #
No Failure
ErrorOr<String> foo = await "2".toErrorOr()
.also(int.parse) // 2
.failIf((val) => val > 2, Errors.validation(description: "$${val} is too big") // 2
.alsoDoAsync((val) => Future.delayed(Duration(milliseconds: val))) // Sleep for 2 milliseconds
.alsoDo((val) => print("Finished waiting $${val} milliseconds.")) // Finished waiting 2 milliseconds.
.alsoAsync((val) => Future.value(val * 2)) // 4
.also((val) => "The result is $${val}") // "The result is 4"
.orElse(errorOnErrorHandler: (errors) => Errors.unexpected(description: "Yikes")) // "The result is 4"
.matchFirst(
(value) => value, // "The result is 4"
(firstError) => "An error occurred: ${firstError.description}");
copied to clipboard
Failure
ErrorOr<String> foo = await "5".ToErrorOr()
.also(int.Parse) // 5
.failIf((val) => val > 2, Errors.validation(description: "${val} is too big")) // Errors.validation()
.alsoDoAsync((val) => Future.delayed(Duration(milliseconds: val))) // Errors.validation()
.alsoDo((val) => print("Finished waiting ${val} milliseconds.")) // Errors.validation()
.alsoAsync((val) => Future.value(val * 2)) // Errors.validation()
.also((val) => "The result is ${val}") // Errors.validation()
.orElse(errorOnErrorHandler: (errors) => Errors.unexpected(description: "Yikes")) // Errors.unexpected()
.matchFirst(
(value) => value,
(firstError) => "An error occurred: {firstError.description}"); // An error occurred: Yikes
copied to clipboard
Creating an ErrorOr instance #
Using The ToErrorOr Extension Method #
ErrorOr<int> result = 5.ToErrorOr();
ErrorOr<int> result = Errors.unexpected().ToErrorOr<int>();
ErrorOr<int> result = [Errors.validation(), Errors.validation()].ToErrorOr<int>();
copied to clipboard
Properties #
isError #
ErrorOr<int> result = User.create();

if (result.isError)
{
// the result contains one or more errors
}
copied to clipboard
value #
ErrorOr<int> result = User.create();

if (!result.isError) // the result contains a value
{
print(result.value);
}
copied to clipboard
errors #
ErrorOr<int> result = User.create();

if (result.isError)
{
result.errors // contains the list of errors that occurred
.forEach((error) => print(error.description));
}
copied to clipboard
firstError #
ErrorOr<int> result = User.create();

if (result.isError)
{
var firstError = result.firstError; // only the first error that occurred
print(firstError == result.errors[0]); // true
}
copied to clipboard
errorsOrEmptyList #
ErrorOr<int> result = User.create();

if (result.isError)
{
result.errorsOrEmptyList // List<Errors> { /* one or more errors */ }
return;
}

result.errorsOrEmptyList // List<Errors> { }
copied to clipboard
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 #
String foo = result.match(
(value) => value,
(errors) => "${errors.Count} errors occurred.");
copied to clipboard
matchAsync #
String foo = await result.matchAsync(
(value) => Future.value(value),
(errors) => Future.value("${errors.Count} errors occurred."));
copied to clipboard
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.
String foo = result.matchFirst(
(value) => value,
(firstError) => firstError.description);
copied to clipboard
matchFirstAsync #
String foo = await result.matchFirstAsync(
(value) => Future.value(value),
(firstError) => Future.value(firstError.description));
copied to clipboard
doSwitch #
The doSwitch 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.
doSwitch #
result.doSwitch(
(value) => print(value),
(errors) => print("${errors.Count} errors occurred."));
copied to clipboard
doSwitchAsync #
await result.doSwitchAsync(
(value) { print(value); return Future.value(true); },
(errors) { print("${errors.Count} errors occurred."); return Future.value(true); });
copied to clipboard
doSwitchFirst #
The doSwitchFirst 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 doSwitch, if the state is error, doSwitchFirst's onError function receives only the first error that occurred, not the entire list of errors.
result.doSwitchFirst(
(value) => print(value),
(firstError) => print(firstError.description));
copied to clipboard
doSwitchFirstAsync #
await result.doSwitchFirstAsync(
(value) { print(value); return Future.value(true); },
(firstError) { print(firstError.description); return Future.value(true); });
copied to clipboard
also #
also #
also receives a function, and invokes it only if the result is not an error.
ErrorOr<int> foo = result
.also((val) => val * 2);
copied to clipboard
Multiple also methods can be chained together.
ErrorOr<String> foo = result
.also((val) => val * 2)
.also((val) => "The result is ${val}");
copied to clipboard
If any of the methods return an error, the chain will break and the errors will be returned.
ErrorOr<int> Foo() => Errors.unexpected();

ErrorOr<String> foo = result
.also((val) => val * 2)
.also((_) => getAnError())
.also((val) => "The result is ${val}") // this function will not be invoked
.also((val) => "The result is ${val}"); // this function will not be invoked
copied to clipboard
alsoAsync #
alsoAsync receives an asynchronous function, and invokes it only if the result is not an error.
ErrorOr<String> foo = await result
.alsoAsync((val) => doSomethingAsync(val))
.alsoAsync((val) => doSomethingElseAsync("The result is ${val}"));
copied to clipboard
alsoDo and alsoDoAsync #
alsoDo and alsoDoAsync are similar to also and alsoAsync, but instead of invoking a function that returns a value, they invoke an action.
ErrorOr<String> foo = result
.alsoDo((val) => print(val))
.alsoDo((val) => print("The result is ${val}"));
copied to clipboard
ErrorOr<String> foo = await result
.alsoDoAsync((val) => Future.delayed(Duration(milliseconds: val)))
.alsoDo((val) => print("Finsihed waiting ${val} seconds."))
.alsoDoAsync((val) => Future.value(val * 2))
.alsoDo((val) => "The result is ${val}");
copied to clipboard
Mixing also, alsoDo, alsoAsync, alsoDoAsync #
You can mix and match also, alsoDo, alsoAsync, alsoDoAsync methods.
ErrorOr<String> foo = await result
.alsoDoAsync((val) => Future.delayed(Duration(milliseconds: val)))
.also((val) => val * 2)
.alsoAsync((val) => doSomethingAsync(val))
.alsoDo((val) => print("Finsihed waiting ${val} seconds."))
.alsoAsync((val) => Future.value(val * 2))
.also((val) => "The result is ${val}");
copied to clipboard
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.
ErrorOr<int> foo = result
.failIf((val) => val > 2, Errors.validation(description: "${val} is too big"));
copied to clipboard
Once an error is returned, the chain will break and the error will be returned.
var result = "2".ToErrorOr()
.also(int.Parse) // 2
.failIf((val) => val > 1, Errors.validation(description: "${val} is too big") // validation error
.also(num => num * 2) // this function will not be invoked
.also(num => num * 2) // this function will not be invoked
copied to clipboard
orElse #
orElse receives a value or a function. If the result is an error, orElse will return the value or invoke the function. Otherwise, it will return the value of the result.
orElse #
ErrorOr<String> foo = result
.orElse(valueOnError: "fallback value");
copied to clipboard
ErrorOr<String> foo = result
.orElse(valueOnErrorHandler: (errors) => "${errors.Count} errors occurred.");
copied to clipboard
orElseAsync #
ErrorOr<String> foo = await result
.orElseAsync(valueOnError: Future.value("fallback value"));
copied to clipboard
ErrorOr<String> foo = await result
.orElseAsync(valueOnErrorHandler: (errors) => Future.value("${errors.Count} errors occurred."));
copied to clipboard
Mixing Features (also, failIf, orElse, doSwitch, match) #
You can mix also, failIf, orElse, doSwitch and match methods together.
ErrorOr<String> foo = await result
.alsoDoAsync((val) => Future.delayed(Duration(milliseconds: val)))
.failIf((val) => val > 2, Errors.validation(description: "${val} is too big"))
.alsoDo((val) => print("Finished waiting ${val} seconds."))
.alsoAsync((val) => Future.value(val * 2))
.also((val) => "The result is ${val}")
.orElse(errorOnErrorHandler: (errors) => Errors.unexpected())
.matchFirst(
(value) => value,
(firstError) => "An error occurred: {firstError.description}");
copied to clipboard
Errors Types #
Each Errors 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:
enum ErrorType {
failure,
unexpected,
validation,
conflict,
notFound,
unauthorized,
forbidden,
}
copied to clipboard
Each error type has a static method that creates an error of that type. For example:
var error = Errors.notFound();
copied to clipboard
optionally, you can pass a code, description and metadata to the error:
var user = Object();
var error = Errors.unexpected(
code: "User.ShouldNeverHappen",
description: "A user error that should never happen",
metadata: Map.fromEntries([
MapEntry("user", user),
]));
copied to clipboard
The ErrorType enum is a good way to categorize errors.
Organizing errors #
A nice approach, is creating a static class with the expected errors. For example:
class DivisionErrors
{
static Errors cannotdivideByZero = Errors.unexpected(
code: "Division.CannotdivideByZero",
description: "Cannot divide by zero.");
}
copied to clipboard
Which can later be used as following 👇
public ErrorOr<double> divide(int a, int b)
{
if (b == 0)
{
return DivisionErrors.cannotdivideByZero;
}

return a / b;
}
copied to clipboard
Contribution 🤲 #
If you have any questions, comments, or suggestions, please open an issue or create a pull request 🙂
Credits 🙏 #

ErrorOr - An awesome library which provides C# style discriminated unions behavior for C#

License #
This project is licensed under the terms of the MIT license.

License:

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Files In This Product:

Customer Reviews

There are no reviews.