Skip to content

Commit

Permalink
Add Chain and ChainAsync
Browse files Browse the repository at this point in the history
  • Loading branch information
amantinband committed Jan 2, 2024
1 parent 0fcad40 commit 06c5adf
Show file tree
Hide file tree
Showing 9 changed files with 491 additions and 359 deletions.
39 changes: 27 additions & 12 deletions src/ErrorOr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,21 +259,36 @@ public async Task<TResult> MatchFirstAsync<TResult>(Func<TValue, Task<TResult>>

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

/// <summary>
/// Provides utility methods for creating instances of <see ref="ErrorOr{T}"/>.
/// </summary>
public static class ErrorOr
{
/// <summary>
/// Creates an <see ref="ErrorOr{TValue}"/> instance from a value.
/// If the state 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>
/// <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)
{
if (IsError)
{
return Errors;
}

return onValue(Value);
}

/// <summary>
/// If the state is a value, the provided function <paramref name="onValue"/> is executed asynchronously and its result is returned.
/// </summary>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="value">The value from which to create an ErrorOr instance.</param>
/// <returns>An <see ref="ErrorOr{TValue}"/> instance containing the specified value.</returns>
public static ErrorOr<TValue> From<TValue>(TValue value)
/// <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)
{
return value;
if (IsError)
{
return Errors;
}

return await onValue(Value);
}
}
19 changes: 19 additions & 0 deletions src/ErrorOrExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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 asynchronously 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>> ChainAsync<TResult, TNextResult>(this Task<ErrorOr<TResult>> errorOr, Func<TResult, Task<ErrorOr<TNextResult>>> onValue)
{
var result = await errorOr;

return await result.ChainAsync(onValue);
}
}
51 changes: 51 additions & 0 deletions tests/ErrorOr.ChainAsyncTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using ErrorOr;
using FluentAssertions;

namespace Tests;

public class ChainAsyncTests
{
record Person(string Name);

[Fact]
public async Task ChainErrorOrsAsync_WhenStateIsValue_ShouldInvokeNextInChain()
{
// Arrange
ErrorOr<Person> errorOrPerson = new Person("Amichai");

static Task<ErrorOr<string>> GetNameAsync(Person person) => Task.FromResult(ErrorOrFactory.From(person.Name));
static Task<ErrorOr<Person>> CreatePersonFromNameAsync(string name) => Task.FromResult(ErrorOrFactory.From(new Person(name)));

// Act
var result = await errorOrPerson
.ChainAsync(person => GetNameAsync(person))
.ChainAsync(name => CreatePersonFromNameAsync(name))
.ChainAsync(person => GetNameAsync(person))
.ChainAsync(name => CreatePersonFromNameAsync(name));

// Assert
result.IsError.Should().BeFalse();
result.Value.Should().BeEquivalentTo(errorOrPerson.Value);
}

[Fact]
public async Task ChainErrorOrsAsync_WhenStateIsError_ShouldReturnErrors()
{
// Arrange
ErrorOr<Person> errorOrPerson = Error.NotFound();

static Task<ErrorOr<string>> GetNameAsync(Person person) => Task.FromResult(ErrorOrFactory.From(person.Name));
static Task<ErrorOr<Person>> CreatePersonFromNameAsync(string name) => Task.FromResult(ErrorOrFactory.From(new Person(name)));

// Act
var result = await errorOrPerson
.ChainAsync(person => GetNameAsync(person))
.ChainAsync(name => CreatePersonFromNameAsync(name))
.ChainAsync(person => GetNameAsync(person))
.ChainAsync(name => CreatePersonFromNameAsync(name));

// Assert
result.IsError.Should().BeTrue();
result.FirstError.Should().BeEquivalentTo(Error.NotFound());
}
}
47 changes: 47 additions & 0 deletions tests/ErrorOr.ChainTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using ErrorOr;
using FluentAssertions;

namespace Tests;

public class ChainTests
{
record Person(string Name);

[Fact]
public void ChainErrorOrs_WhenHasValue_ShouldInvokeNextInChain()
{
// Arrange
ErrorOr<Person> errorOrPerson = new Person("Amichai");

static ErrorOr<string> GetName(Person person) => person.Name;
static ErrorOr<Person> CreatePersonFromName(string name) => new Person(name);

// Act
ErrorOr<Person> result = errorOrPerson
.Chain(person => GetName(person))
.Chain(name => CreatePersonFromName(name));

// Assert
result.IsError.Should().BeFalse();
result.Value.Should().BeEquivalentTo(errorOrPerson.Value);
}

[Fact]
public void ChainErrorOrs_WhenHasError_ShouldReturnErrors()
{
// Arrange
ErrorOr<Person> errorOrPerson = Error.NotFound();

static ErrorOr<string> GetName(Person person) => person.Name;
static ErrorOr<Person> CreatePersonFromName(string name) => new Person(name);

// Act
ErrorOr<Person> result = errorOrPerson
.Chain(person => GetName(person))
.Chain(name => CreatePersonFromName(name));

// Assert
result.IsError.Should().BeTrue();
result.FirstError.Should().BeEquivalentTo(Error.NotFound());
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 06c5adf

Please sign in to comment.