Skip to content

Commit

Permalink
Merge pull request #80 from amantinband/feature/add-then-that-doesnt-…
Browse files Browse the repository at this point in the history
…return-error-or

Add Then/ThenAsync support for functions that don't explicitly return…
  • Loading branch information
amantinband authored Jan 5, 2024
2 parents 1285a3f + f09e070 commit dae4251
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 283 deletions.
32 changes: 32 additions & 0 deletions src/ErrorOr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,22 @@ public ErrorOr<TResult> Then<TResult>(Func<TValue, ErrorOr<TResult>> onValue)
return onValue(Value);
}

/// <summary>
/// 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> Then<TResult>(Func<TValue, 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>
Expand All @@ -292,6 +308,22 @@ public async Task<ErrorOr<TResult>> ThenAsync<TResult>(Func<TValue, Task<ErrorOr
return await onValue(Value).ConfigureAwait(false);
}

/// <summary>
/// If the state 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>
/// <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>> ThenAsync<TResult>(Func<TValue, Task<TResult>> onValue)
{
if (IsError)
{
return Errors;
}

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>
Expand Down
30 changes: 30 additions & 0 deletions src/ErrorOrExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ public static async Task<ErrorOr<TNextResult>> Then<TResult, TNextResult>(this T
return result.Then(onValue);
}

/// <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, 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 @@ -32,6 +47,21 @@ public static async Task<ErrorOr<TNextResult>> ThenAsync<TResult, TNextResult>(t
return await result.ThenAsync(onValue).ConfigureAwait(false);
}

/// <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>> ThenAsync<TResult, TNextResult>(this Task<ErrorOr<TResult>> errorOr, Func<TResult, Task<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>
Expand Down
196 changes: 98 additions & 98 deletions tests/ErrorOr.MatchAsyncTests.cs
Original file line number Diff line number Diff line change
@@ -1,98 +1,98 @@
using ErrorOr;
using FluentAssertions;

namespace Tests;

public class MatchAsyncTests
{
private record Person(string Name);

[Fact]
public async Task MatchAsyncErrorOr_WhenHasValue_ShouldExecuteOnValueAction()
{
// Arrange
ErrorOr<Person> errorOrPerson = new Person("Amichai");
Task<string> OnValueAction(Person person)
{
person.Should().BeEquivalentTo(errorOrPerson.Value);
return Task.FromResult("Nice");
}

Task<string> OnErrorsAction(IReadOnlyList<Error> _) => throw new Exception("Should not be called");

// Act
var action = async () => await errorOrPerson.MatchAsync(
OnValueAction,
OnErrorsAction);

// Assert
(await action.Should().NotThrowAsync()).Subject.Should().Be("Nice");
}

[Fact]
public async Task MatchAsyncErrorOr_WhenHasError_ShouldExecuteOnErrorAction()
{
// Arrange
ErrorOr<Person> errorOrPerson = new List<Error> { Error.Validation(), Error.Conflict() };
Task<string> OnValueAction(Person _) => throw new Exception("Should not be called");

Task<string> OnErrorsAction(IReadOnlyList<Error> errors)
{
errors.Should().BeEquivalentTo(errorOrPerson.Errors);
return Task.FromResult("Nice");
}

// Act
var action = async () => await errorOrPerson.MatchAsync(
OnValueAction,
OnErrorsAction);

// Assert
(await action.Should().NotThrowAsync()).Subject.Should().Be("Nice");
}

[Fact]
public async Task MatchFirstAsyncErrorOr_WhenHasValue_ShouldExecuteOnValueAction()
{
// Arrange
ErrorOr<Person> errorOrPerson = new Person("Amichai");
Task<string> OnValueAction(Person person)
{
person.Should().BeEquivalentTo(errorOrPerson.Value);
return Task.FromResult("Nice");
}

Task<string> OnFirstErrorAction(Error _) => throw new Exception("Should not be called");

// Act
var action = async () => await errorOrPerson.MatchFirstAsync(
OnValueAction,
OnFirstErrorAction);

// Assert
(await action.Should().NotThrowAsync()).Subject.Should().Be("Nice");
}

[Fact]
public async Task MatchFirstAsyncErrorOr_WhenHasError_ShouldExecuteOnFirstErrorAction()
{
// Arrange
ErrorOr<Person> errorOrPerson = new List<Error> { Error.Validation(), Error.Conflict() };
Task<string> OnValueAction(Person _) => throw new Exception("Should not be called");
Task<string> OnFirstErrorAction(Error errors)
{
errors.Should().BeEquivalentTo(errorOrPerson.Errors[0])
.And.BeEquivalentTo(errorOrPerson.FirstError);

return Task.FromResult("Nice");
}

// Act
var action = async () => await errorOrPerson.MatchFirstAsync(
OnValueAction,
OnFirstErrorAction);

// Assert
(await action.Should().NotThrowAsync()).Subject.Should().Be("Nice");
}
}
using ErrorOr;
using FluentAssertions;

namespace Tests;

public class MatchAsyncTests
{
private record Person(string Name);

[Fact]
public async Task MatchAsyncErrorOr_WhenIsSuccess_ShouldExecuteOnValueAction()
{
// Arrange
ErrorOr<Person> errorOrPerson = new Person("Amichai");
Task<string> OnValueAction(Person person)
{
person.Should().BeEquivalentTo(errorOrPerson.Value);
return Task.FromResult("Nice");
}

Task<string> OnErrorsAction(IReadOnlyList<Error> _) => throw new Exception("Should not be called");

// Act
var action = async () => await errorOrPerson.MatchAsync(
OnValueAction,
OnErrorsAction);

// Assert
(await action.Should().NotThrowAsync()).Subject.Should().Be("Nice");
}

[Fact]
public async Task MatchAsyncErrorOr_WhenIsError_ShouldExecuteOnErrorAction()
{
// Arrange
ErrorOr<Person> errorOrPerson = new List<Error> { Error.Validation(), Error.Conflict() };
Task<string> OnValueAction(Person _) => throw new Exception("Should not be called");

Task<string> OnErrorsAction(IReadOnlyList<Error> errors)
{
errors.Should().BeEquivalentTo(errorOrPerson.Errors);
return Task.FromResult("Nice");
}

// Act
var action = async () => await errorOrPerson.MatchAsync(
OnValueAction,
OnErrorsAction);

// Assert
(await action.Should().NotThrowAsync()).Subject.Should().Be("Nice");
}

[Fact]
public async Task MatchFirstAsyncErrorOr_WhenIsSuccess_ShouldExecuteOnValueAction()
{
// Arrange
ErrorOr<Person> errorOrPerson = new Person("Amichai");
Task<string> OnValueAction(Person person)
{
person.Should().BeEquivalentTo(errorOrPerson.Value);
return Task.FromResult("Nice");
}

Task<string> OnFirstErrorAction(Error _) => throw new Exception("Should not be called");

// Act
var action = async () => await errorOrPerson.MatchFirstAsync(
OnValueAction,
OnFirstErrorAction);

// Assert
(await action.Should().NotThrowAsync()).Subject.Should().Be("Nice");
}

[Fact]
public async Task MatchFirstAsyncErrorOr_WhenIsError_ShouldExecuteOnFirstErrorAction()
{
// Arrange
ErrorOr<Person> errorOrPerson = new List<Error> { Error.Validation(), Error.Conflict() };
Task<string> OnValueAction(Person _) => throw new Exception("Should not be called");
Task<string> OnFirstErrorAction(Error errors)
{
errors.Should().BeEquivalentTo(errorOrPerson.Errors[0])
.And.BeEquivalentTo(errorOrPerson.FirstError);

return Task.FromResult("Nice");
}

// Act
var action = async () => await errorOrPerson.MatchFirstAsync(
OnValueAction,
OnFirstErrorAction);

// Assert
(await action.Should().NotThrowAsync()).Subject.Should().Be("Nice");
}
}
Loading

0 comments on commit dae4251

Please sign in to comment.