Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Chain and ChainAsync #77

Merged
merged 4 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add Then, ThenAsync, Else, ElseAsync
  • Loading branch information
amantinband committed Jan 3, 2024
commit 747c0411bb0f63873fe1be5168f697581f518110
73 changes: 55 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
- [`MatchFirst` / `MatchFirstAsync`](#matchfirst--matchfirstasync)
- [`Switch` / `SwitchAsync`](#switch--switchasync)
- [`SwitchFirst` / `SwitchFirstAsync`](#switchfirst--switchfirstasync)
- [`Chain` / `ChainAsync`](#chain--chainasync)
- [`Then` / `ThenAsync`](#then--thenasync)
- [`Else` / `ElseAsync`](#else--elseasync)
- [Error Types](#error-types)
- [Built-in Error Types](#built-in-error-types)
- [Custom error types](#custom-error-types)
Expand Down Expand Up @@ -510,9 +511,9 @@ await errorOrString.SwitchFirstAsync(
firstError => { Console.WriteLine(firstError.Description); return Task.CompletedTask; });
```

### `Chain` / `ChainAsync`
### `Then` / `ThenAsync`

Multiple methods that return `ErrorOr<T>` can be chained as follows
Multiple methods that return `ErrorOr<T>` can be chained as follows:

```csharp
static ErrorOr<string> ConvertToString(int num) => num.ToString();
Expand All @@ -521,30 +522,66 @@ static ErrorOr<int> ConvertToInt(string str) => int.Parse(str);
ErrorOr<string> errorOrString = "5";

ErrorOr<string> result = errorOrString
.Chain(str => ConvertToInt(str))
.Chain(num => ConvertToString(num))
.Chain(str => ConvertToInt(str))
.Chain(num => ConvertToString(num))
.Chain(str => ConvertToInt(str))
.Chain(num => ConvertToString(num));
.Then(str => ConvertToInt(str))
.Then(num => ConvertToString(num))
.Then(str => ConvertToInt(str))
.Then(num => ConvertToString(num));
```

```csharp
static Task<ErrorOr<string>> ConvertToString(int num) => Task.FromResult(ErrorOrFactory.From(num.ToString()));
static Task<ErrorOr<int>> ConvertToInt(string str) => Task.FromResult(ErrorOrFactory.From(int.Parse(str)));
static ErrorOr<string> ConvertToString(int num) => num.ToString();
static Task<ErrorOr<string>> ConvertToStringAsync(int num) => Task.FromResult(ErrorOrFactory.From(num.ToString()));
static Task<ErrorOr<int>> ConvertToIntAsync(string str) => Task.FromResult(ErrorOrFactory.From(int.Parse(str)));

ErrorOr<string> errorOrString = "5";

ErrorOr<string> result = await errorOrString
.ChainAsync(str => ConvertToInt(str))
.ChainAsync(num => ConvertToString(num))
.ChainAsync(str => ConvertToInt(str))
.ChainAsync(num => ConvertToString(num))
.ChainAsync(str => ConvertToInt(str))
.ChainAsync(num => ConvertToString(num));
.ThenAsync(str => ConvertToIntAsync(str))
.ThenAsync(num => ConvertToStringAsync(num))
.ThenAsync(str => ConvertToIntAsync(str))
.ThenAsync(num => ConvertToStringAsync(num));

// mixing `ThenAsync` and `Then`
ErrorOr<string> result = await errorOrString
.ThenAsync(str => ConvertToIntAsync(str))
.Then(num => ConvertToString(num))
.ThenAsync(str => ConvertToIntAsync(str))
.Then(num => ConvertToString(num));
```

If any of the methods return an error, the chain will be broken and the error will be returned.
If any of the methods return an error, the chain will break and the errors will be returned.

### `Else` / `ElseAsync`

The `Else` / `ElseAsync` methods can be used to specify a fallback value in case the state is error anywhere in the chain.

```csharp
// ignoring the errors
string result = errorOrString
.Then(str => ConvertToInt(str))
.Then(num => ConvertToString(num))
.Else("fallback value");

// using the errors
string result = errorOrString
.Then(str => ConvertToInt(str))
.Then(num => ConvertToString(num))
.Else(errors => $"{errors.Count} errors occurred.");
```

```csharp
// ignoring the errors
string result = await errorOrString
.ThenAsync(str => ConvertToInt(str))
.ThenAsync(num => ConvertToString(num))
.ElseAsync(Task.FromResult("fallback value"));

// using the errors
string result = await errorOrString
.ThenAsync(str => ConvertToInt(str))
.ThenAsync(num => ConvertToString(num))
.ElseAsync(errors => Task.FromResult($"{errors.Count} errors occurred."));
```

## Error Types

Expand Down
64 changes: 62 additions & 2 deletions src/ErrorOr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ public async Task<TResult> MatchFirstAsync<TResult>(Func<TValue, Task<TResult>>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="onValue">The function to execute if the state is a value.</param>
/// <returns>The result from calling <paramref name="onValue"/> if state is value; otherwise the original <see cref="Errors"/>.</returns>
public ErrorOr<TResult> Chain<TResult>(Func<TValue, ErrorOr<TResult>> onValue)
public ErrorOr<TResult> Then<TResult>(Func<TValue, ErrorOr<TResult>> onValue)
{
if (IsError)
{
Expand All @@ -282,7 +282,7 @@ public ErrorOr<TResult> Chain<TResult>(Func<TValue, ErrorOr<TResult>> onValue)
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="onValue">The function to execute if the state is a value.</param>
/// <returns>The result from calling <paramref name="onValue"/> if state is value; otherwise the original <see cref="Errors"/>.</returns>
public async Task<ErrorOr<TResult>> ChainAsync<TResult>(Func<TValue, Task<ErrorOr<TResult>>> onValue)
public async Task<ErrorOr<TResult>> ThenAsync<TResult>(Func<TValue, Task<ErrorOr<TResult>>> onValue)
{
if (IsError)
{
Expand All @@ -291,4 +291,64 @@ public async Task<ErrorOr<TResult>> ChainAsync<TResult>(Func<TValue, Task<ErrorO

return await onValue(Value).ConfigureAwait(false);
}

/// <summary>
/// If the state is error, the provided function <paramref name="onError"/> is executed and its result is returned.
/// </summary>
/// <param name="onError">The function to execute if the state is error.</param>
/// <returns>The result from calling <paramref name="onError"/> if state is value; otherwise the original <see cref="Errors"/>.</returns>
amantinband marked this conversation as resolved.
Show resolved Hide resolved
public TValue Else(Func<List<Error>, TValue> onError)
{
if (!IsError)
{
return Value;
}

return onError(Errors);
}

/// <summary>
/// If the state is error, the provided function <paramref name="onError"/> is executed and its result is returned.
/// </summary>
/// <param name="onError">The value to return if the state is error.</param>
/// <returns>The result from calling <paramref name="onError"/> if state is value; otherwise the original <see cref="Errors"/>.</returns>
public TValue Else(TValue onError)
{
if (!IsError)
{
return Value;
}

return onError;
}

/// <summary>
/// If the state is error, the provided function <paramref name="onError"/> is executed asynchronously and its result is returned.
/// </summary>
/// <param name="onError">The function to execute if the state is error.</param>
/// <returns>The result from calling <paramref name="onError"/> if state is value; otherwise the original <see cref="Errors"/>.</returns>
public async Task<TValue> ElseAsync(Func<List<Error>, Task<TValue>> onError)
{
if (!IsError)
{
return Value;
}

return await onError(Errors).ConfigureAwait(false);
}

/// <summary>
/// If the state is error, the provided function <paramref name="onError"/> is executed asynchronously and its result is returned.
/// </summary>
/// <param name="onError">The function to execute if the state is error.</param>
/// <returns>The result from calling <paramref name="onError"/> if state is value; otherwise the original <see cref="Errors"/>.</returns>
public async Task<TValue> ElseAsync(Task<TValue> onError)
{
if (!IsError)
{
return Value;
}

return await onError.ConfigureAwait(false);
}
}
73 changes: 70 additions & 3 deletions src/ErrorOrExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@ namespace ErrorOr;

public static class ErrorOrExtensions
{
/// <summary>
/// If the state of <paramref name="errorOr"/> is a value, the provided function <paramref name="onValue"/> is executed and its result is returned.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <typeparam name="TNextResult">The type of the next result.</typeparam>
/// <param name="errorOr">The error.</param>
/// <param name="onValue">The function to execute if the state is a value.</param>
/// <returns>The result from calling <paramref name="onValue"/> if state is value; otherwise the original errors.</returns>
public static async Task<ErrorOr<TNextResult>> Then<TResult, TNextResult>(this Task<ErrorOr<TResult>> errorOr, Func<TResult, ErrorOr<TNextResult>> onValue)
{
var result = await errorOr.ConfigureAwait(false);

return result.Then(onValue);
}

/// <summary>
/// If the state of <paramref name="errorOr"/> is a value, the provided function <paramref name="onValue"/> is executed asynchronously and its result is returned.
/// </summary>
Expand All @@ -10,10 +25,62 @@ public static class ErrorOrExtensions
/// <param name="errorOr">The error.</param>
/// <param name="onValue">The function to execute if the state is a value.</param>
/// <returns>The result from calling <paramref name="onValue"/> if state is value; otherwise the original errors.</returns>
public static async Task<ErrorOr<TNextResult>> ChainAsync<TResult, TNextResult>(this Task<ErrorOr<TResult>> errorOr, Func<TResult, Task<ErrorOr<TNextResult>>> onValue)
public static async Task<ErrorOr<TNextResult>> ThenAsync<TResult, TNextResult>(this Task<ErrorOr<TResult>> errorOr, Func<TResult, Task<ErrorOr<TNextResult>>> onValue)
{
var result = await errorOr.ConfigureAwait(false);

return await result.ThenAsync(onValue).ConfigureAwait(false);
}

/// <summary>
/// If the state is error, the provided function <paramref name="onError"/> is executed asynchronously and its result is returned.
/// </summary>
/// <param name="errorOr">The error.</param>
/// <param name="onError">The function to execute if the state is error.</param>
/// <returns>The result from calling <paramref name="onError"/> if state is value; otherwise the original errors.</returns>
public static async Task<TValue> Else<TValue>(this Task<ErrorOr<TValue>> errorOr, Func<List<Error>, TValue> onError)
{
var result = await errorOr.ConfigureAwait(false);

return result.Else(onError);
}

/// <summary>
/// If the state is error, the provided function <paramref name="onError"/> is executed asynchronously and its result is returned.
/// </summary>
/// <param name="errorOr">The error.</param>
/// <param name="onError">The function to execute if the state is error.</param>
/// <returns>The result from calling <paramref name="onError"/> if state is value; otherwise the original errors.</returns>
public static async Task<TValue> Else<TValue>(this Task<ErrorOr<TValue>> errorOr, TValue onError)
{
var result = await errorOr.ConfigureAwait(false);

return result.Else(onError);
}

/// <summary>
/// If the state is error, the provided function <paramref name="onError"/> is executed asynchronously and its result is returned.
/// </summary>
/// <param name="errorOr">The error.</param>
/// <param name="onError">The function to execute if the state is error.</param>
/// <returns>The result from calling <paramref name="onError"/> if state is value; otherwise the original errors.</returns>
public static async Task<TValue> ElseAsync<TValue>(this Task<ErrorOr<TValue>> errorOr, Func<List<Error>, Task<TValue>> onError)
{
var result = await errorOr.ConfigureAwait(false);

return await result.ElseAsync(onError).ConfigureAwait(false);
}

/// <summary>
/// If the state is error, the provided function <paramref name="onError"/> is executed asynchronously and its result is returned.
/// </summary>
/// <param name="errorOr">The error.</param>
/// <param name="onError">The function to execute if the state is error.</param>
/// <returns>The result from calling <paramref name="onError"/> if state is value; otherwise the original errors.</returns>
public static async Task<TValue> ElseAsync<TValue>(this Task<ErrorOr<TValue>> errorOr, Task<TValue> onError)
{
var result = await errorOr;
var result = await errorOr.ConfigureAwait(false);

return await result.ChainAsync(onValue).ConfigureAwait(false);
return await result.ElseAsync(onError).ConfigureAwait(false);
}
}
51 changes: 0 additions & 51 deletions tests/ErrorOr.ChainAsyncTests.cs

This file was deleted.

47 changes: 0 additions & 47 deletions tests/ErrorOr.ChainTests.cs

This file was deleted.

Loading
Loading