diff --git a/src/Ardalis.Result/ResultExtensions.cs b/src/Ardalis.Result/ResultExtensions.cs
index 5efca42..c8fb2d1 100644
--- a/src/Ardalis.Result/ResultExtensions.cs
+++ b/src/Ardalis.Result/ResultExtensions.cs
@@ -1,47 +1,338 @@
using System;
using System.Linq;
+using System.Threading.Tasks;
namespace Ardalis.Result
{
public static class ResultExtensions
{
///
- /// Transforms a Result's type from a source type to a destination type. If the Result is successful, the func parameter is invoked on the Result's source value to map it to a destination type.
+ /// Transforms a Result's type from a source type to a destination type.
+ /// If the Result is successful, the func parameter is invoked on the Result's source value to map it to a destination type.
///
- ///
- ///
- ///
- ///
- ///
- ///
+ /// The type of the value contained in the source Result.
+ /// The type of the value to be returned in the destination Result.
+ /// The source Result to transform.
+ /// A function to transform the source value to the destination type.
+ /// A Result containing the transformed value or the appropriate error status.
+ /// Thrown when the Result status is not supported.
public static Result Map(this Result result, Func func)
{
- switch (result.Status)
- {
- case ResultStatus.Ok: return func(result);
- case ResultStatus.Created: return string.IsNullOrEmpty(result.Location)
- ? Result.Created(func(result.Value))
- : Result.Created(func(result.Value), result.Location);
- case ResultStatus.NotFound: return result.Errors.Any()
- ? Result.NotFound(result.Errors.ToArray())
- : Result.NotFound();
- case ResultStatus.Unauthorized: return result.Errors.Any()
- ? Result.Unauthorized(result.Errors.ToArray())
- : Result.Unauthorized();
- case ResultStatus.Forbidden: return result.Errors.Any()
- ? Result.Forbidden(result.Errors.ToArray())
- : Result.Forbidden();
- case ResultStatus.Invalid: return Result.Invalid(result.ValidationErrors);
- case ResultStatus.Error: return Result.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId));
- case ResultStatus.Conflict: return result.Errors.Any()
- ? Result.Conflict(result.Errors.ToArray())
- : Result.Conflict();
- case ResultStatus.CriticalError: return Result.CriticalError(result.Errors.ToArray());
- case ResultStatus.Unavailable: return Result.Unavailable(result.Errors.ToArray());
- case ResultStatus.NoContent: return Result.NoContent();
- default:
- throw new NotSupportedException($"Result {result.Status} conversion is not supported.");
- }
+ return result.Status switch
+ {
+ ResultStatus.Ok => (Result)func(result),
+ ResultStatus.Created => string.IsNullOrEmpty(result.Location)
+ ? Result.Created(func(result.Value))
+ : Result.Created(func(result.Value), result.Location),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static Result Map(this Result result, Func func)
+ {
+ return result.Status switch
+ {
+ ResultStatus.Ok => Result.Success(func()),
+ ResultStatus.Created => string.IsNullOrEmpty(result.Location)
+ ? Result.Created(func())
+ : Result.Created(func(), result.Location),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task> MapAsync(
+ this Result result,
+ Func> func)
+ {
+ return result.Status switch
+ {
+ ResultStatus.Ok => Result.Success(await func(result.Value)),
+ ResultStatus.Created => string.IsNullOrEmpty(result.Location)
+ ? Result.Created(await func(result.Value))
+ : Result.Created(await func(result.Value), result.Location),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+
+ public static async Task> MapAsync(
+ this Task> resultTask,
+ Func> func)
+ {
+ var result = await resultTask;
+ return result.Status switch
+ {
+ ResultStatus.Ok => Result.Success(await func(result.Value)),
+ ResultStatus.Created => string.IsNullOrEmpty(result.Location)
+ ? Result.Created(await func(result.Value))
+ : Result.Created(await func(result.Value), result.Location),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task> MapAsync(
+ this Result result,
+ Func> func)
+ {
+ return result.Status switch
+ {
+ ResultStatus.Ok => Result.Success(await func()),
+ ResultStatus.Created => string.IsNullOrEmpty(result.Location)
+ ? Result.Created(await func())
+ : Result.Created(await func(), result.Location),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task> MapAsync(
+ this Task resultTask,
+ Func> func)
+ {
+ var result = await resultTask;
+ return await result.MapAsync(func);
+ }
+
+
+ public static async Task> MapAsync(
+ this Task> resultTask,
+ Func func)
+ {
+ var result = await resultTask;
+ return result.Map(func);
+ }
+
+ public static async Task> MapAsync(
+ this Task resultTask,
+ Func func)
+ {
+ var result = await resultTask;
+ return result.Map(func);
+ }
+
+ ///
+ /// Transforms a Result's type from a source type to a destination type.
+ /// If the Result is successful, the func parameter is invoked on the Result's source value to map it to a destination type.
+ ///
+ /// The type of the value contained in the source Result.
+ /// The type of the value to be returned in the destination Result.
+ /// The source Result to transform.
+ /// A function to transform the source value to the destination type.
+ /// A Result containing the transformed value or the appropriate error status.
+ /// Thrown when the Result status is not supported.
+ public static Result Bind(this Result result, Func> bindFunc)
+ {
+ return result.Status switch
+ {
+ ResultStatus.Ok => bindFunc(result.Value),
+ ResultStatus.Created => bindFunc(result.Value),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static Result Bind(this Result result, Func> bindFunc)
+ {
+ return result.Status switch
+ {
+ ResultStatus.Ok => bindFunc(result.Value),
+ ResultStatus.Created => bindFunc(result.Value),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static Result Bind(this Result result, Func, Result> bindFunc)
+ {
+ return result.Status switch
+ {
+ ResultStatus.Ok => bindFunc(result.Value),
+ ResultStatus.Created => bindFunc(result.Value),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task> BindAsync(
+ this Task> resultTask,
+ Func>> bindFunc)
+ {
+ var result = await resultTask;
+ return result.Status switch
+ {
+ ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
+ ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task BindAsync(
+ this Task> resultTask,
+ Func> bindFunc)
+ {
+ var result = await resultTask;
+ return result.Status switch
+ {
+ ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
+ ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task BindAsync(
+ this Result result,
+ Func> bindFunc)
+ {
+ return result.Status switch
+ {
+ ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
+ ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task BindAsync(
+ this Result result,
+ Func> bindFunc)
+ {
+ return result.Status switch
+ {
+ ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
+ ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task> BindAsync(
+ this Task resultTask,
+ Func>> bindFunc)
+ {
+ var result = await resultTask;
+ return result.Status switch
+ {
+ ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
+ ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task> BindAsync(
+ this Result result,
+ Func>> bindFunc)
+ {
+ return result.Status switch
+ {
+ ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
+ ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task BindAsync(
+ this Result result,
+ Func> bindFunc)
+ {
+ return result.Status switch
+ {
+ ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
+ ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task BindAsync(this Task resultTask, Func> bindFunc)
+ {
+ var result = await resultTask;
+ return result.Status switch
+ {
+ ResultStatus.Ok => await bindFunc(result).ConfigureAwait(false),
+ ResultStatus.Created => await bindFunc(result).ConfigureAwait(false),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ public static async Task> BindAsync(
+ this Task> resultTask,
+ Func> bindFunc)
+ {
+ var result = await resultTask;
+ return result.Status switch
+ {
+ ResultStatus.Ok => bindFunc(result.Value),
+ ResultStatus.Created => bindFunc(result.Value),
+ _ => HandleNonSuccessStatus(result),
+ };
+ }
+
+ private static Result HandleNonSuccessStatus(Result result)
+ {
+ return result.Status switch
+ {
+ ResultStatus.NotFound => result.Errors.Any()
+ ? Result.NotFound(result.Errors.ToArray())
+ : Result.NotFound(),
+ ResultStatus.Unauthorized => result.Errors.Any()
+ ? Result.Unauthorized(result.Errors.ToArray())
+ : Result.Unauthorized(),
+ ResultStatus.Forbidden => result.Errors.Any()
+ ? Result.Forbidden(result.Errors.ToArray())
+ : Result.Forbidden(),
+ ResultStatus.Invalid => Result.Invalid(result.ValidationErrors),
+ ResultStatus.Error => Result.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId)),
+ ResultStatus.Conflict => result.Errors.Any()
+ ? Result.Conflict(result.Errors.ToArray())
+ : Result.Conflict(),
+ ResultStatus.CriticalError => Result.CriticalError(result.Errors.ToArray()),
+ ResultStatus.Unavailable => Result.Unavailable(result.Errors.ToArray()),
+ ResultStatus.NoContent => Result.NoContent(),
+ _ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
+ };
+ }
+
+ private static Result HandleNonSuccessStatus(Result result)
+ {
+ return result.Status switch
+ {
+ ResultStatus.NotFound => result.Errors.Any()
+ ? Result.NotFound(result.Errors.ToArray())
+ : Result.NotFound(),
+ ResultStatus.Unauthorized => result.Errors.Any()
+ ? Result.Unauthorized(result.Errors.ToArray())
+ : Result.Unauthorized(),
+ ResultStatus.Forbidden => result.Errors.Any()
+ ? Result.Forbidden(result.Errors.ToArray())
+ : Result.Forbidden(),
+ ResultStatus.Invalid => Result.Invalid(result.ValidationErrors),
+ ResultStatus.Error => Result.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId)),
+ ResultStatus.Conflict => result.Errors.Any()
+ ? Result.Conflict(result.Errors.ToArray())
+ : Result.Conflict(),
+ ResultStatus.CriticalError => Result.CriticalError(result.Errors.ToArray()),
+ ResultStatus.Unavailable => Result.Unavailable(result.Errors.ToArray()),
+ ResultStatus.NoContent => Result.NoContent(),
+ _ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
+ };
+ }
+
+ private static Result HandleNonSuccessStatus(Result result)
+ {
+ return result.Status switch
+ {
+ ResultStatus.NotFound => result.Errors.Any()
+ ? Result.NotFound(result.Errors.ToArray())
+ : Result.NotFound(),
+ ResultStatus.Unauthorized => result.Errors.Any()
+ ? Result.Unauthorized(result.Errors.ToArray())
+ : Result.Unauthorized(),
+ ResultStatus.Forbidden => result.Errors.Any()
+ ? Result.Forbidden(result.Errors.ToArray())
+ : Result.Forbidden(),
+ ResultStatus.Invalid => Result.Invalid(result.ValidationErrors),
+ ResultStatus.Error => Result.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId)),
+ ResultStatus.Conflict => result.Errors.Any()
+ ? Result.Conflict(result.Errors.ToArray())
+ : Result.Conflict(),
+ ResultStatus.CriticalError => Result.CriticalError(result.Errors.ToArray()),
+ ResultStatus.Unavailable => Result.Unavailable(result.Errors.ToArray()),
+ ResultStatus.NoContent => Result.NoContent(),
+ _ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
+ };
}
}
}
diff --git a/tests/Ardalis.Result.UnitTests/ResultBind.cs b/tests/Ardalis.Result.UnitTests/ResultBind.cs
new file mode 100644
index 0000000..febaab9
--- /dev/null
+++ b/tests/Ardalis.Result.UnitTests/ResultBind.cs
@@ -0,0 +1,381 @@
+using FluentAssertions;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Ardalis.Result.UnitTests
+{
+ public class ResultBind
+ {
+ [Fact]
+ public void ShouldProduceSuccessResultFromSuccess()
+ {
+ int successValue = 123;
+ var result = Result.Success(successValue);
+ var expected = Result.Success(successValue.ToString());
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void CanChainSeveralMethods()
+ {
+ var result = Result.Success(123);
+ var expected = Result.Success("125");
+
+ var actual = result.Bind(v => Result.Success(v + 1))
+ .Bind(v => Result.Success(v + 1))
+ .Bind(v => Result.Success(v.ToString()));
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void CanChangeVoidResultToResult()
+ {
+ var result = Result.Success();
+ var expected = Result.Success("Success");
+
+ var actual = result.Bind(_ => Result.Success("Success"));
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void CanBindResultToVoidResult()
+ {
+ var result = Result.Success(123);
+ var expected = Result.Success();
+
+ var actual = result.Bind(_ => Result.Success());
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void ShouldProduceComplexTypeResultFromSuccessAnonymousFunction()
+ {
+ var foo = new Foo("Bar");
+ var result = Result.Success(foo);
+ var expected = Result.Success(new FooDto(foo.Bar));
+
+ var actual = result.Bind(foo => Result.Success(new FooDto(foo.Bar)));
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void ShouldProduceComplexTypeResultFromSuccessWithMethod()
+ {
+ var foo = new Foo("Bar");
+ var result = Result.Success(foo);
+ var expected = Result.Success(FooDto.CreateFromFooResult(foo));
+
+ var actual = result.Bind(FooDto.CreateFromFooResult);
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void ShouldProduceCreatedResultFromCreated()
+ {
+ int createdValue = 123;
+ var result = Result.Created(createdValue);
+ var expected = Result.Created(createdValue.ToString());
+
+ var actual = result.Bind(val => Result.Created(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Created);
+ actual.Value.Should().Be(expected.Value);
+ }
+
+ [Fact]
+ public void ShouldProduceComplexTypeResultFromCreatedAnonymously()
+ {
+ var foo = new Foo("Bar");
+ var result = Result.Created(foo);
+ var expected = Result.Created(new FooDto(foo.Bar));
+
+ var actual = result.Bind(foo => Result.Created(new FooDto(foo.Bar)));
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void ShouldProduceComplexTypeResultFromCreatedWithMethod()
+ {
+ var foo = new Foo("Bar");
+ var result = Result.Created(foo);
+ var expected = Result.Created(FooDto.CreateFromFooResult(foo));
+
+ var actual = result.Bind(FooDto.CreateFromFooCreatedResult);
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void ShouldProduceNotFound()
+ {
+ var result = Result.NotFound();
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.NotFound);
+ actual.Value.Should().BeNull();
+ }
+
+ [Fact]
+ public void ShouldProduceNotFoundWithError()
+ {
+ string expectedMessage = "Some integer not found";
+ var result = Result.NotFound(expectedMessage);
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.NotFound);
+ actual.Errors.Single().Should().Be(expectedMessage);
+ }
+
+ [Fact]
+ public void ShouldProduceUnauthorized()
+ {
+ var result = Result.Unauthorized();
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Unauthorized);
+ }
+
+ [Fact]
+ public void ShouldProduceForbidden()
+ {
+ var result = Result.Forbidden();
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Forbidden);
+ }
+
+ [Fact]
+ public void ShouldProduceInvalidWithValidationErrors()
+ {
+ var validationErrors = new List
+ {
+ new() { ErrorMessage = "Validation Error 1" },
+ new() { ErrorMessage = "Validation Error 2" }
+ };
+ var result = Result.Invalid(validationErrors);
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Invalid);
+ actual.ValidationErrors.Should().BeEquivalentTo(validationErrors);
+ }
+
+ [Fact]
+ public void ShouldProduceInvalidWithoutValidationErrors()
+ {
+ var validationErrors = new List();
+ var result = Result.Invalid(validationErrors);
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Invalid);
+ actual.ValidationErrors.Should().BeEmpty();
+ }
+
+ [Fact]
+ public void ShouldProduceErrorResultWithErrors()
+ {
+ var errorList = new ErrorList(new[] { "Error 1", "Error 2" }, default);
+ var result = Result.Error(errorList);
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Error);
+ actual.Errors.Should().BeEquivalentTo(errorList.ErrorMessages);
+ }
+
+ [Fact]
+ public void ShouldProduceErrorResultWithNoErrors()
+ {
+ var result = Result.Error();
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Error);
+ actual.Errors.Should().BeEmpty();
+ actual.CorrelationId.Should().BeEmpty();
+ }
+
+ [Fact]
+ public void ShouldProduceConflict()
+ {
+ var result = Result.Conflict();
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Conflict);
+ }
+
+ [Fact]
+ public void ShouldProduceConflictWithError()
+ {
+ string expectedMessage = "Some conflict";
+ var result = Result.Conflict(expectedMessage);
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Conflict);
+ actual.Errors.Single().Should().Be(expectedMessage);
+ }
+
+ [Fact]
+ public void ShouldProduceUnavailableWithError()
+ {
+ string expectedMessage = "Something unavailable";
+ var result = Result.Unavailable(expectedMessage);
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Unavailable);
+ actual.Errors.Single().Should().Be(expectedMessage);
+ }
+
+ [Fact]
+ public void ShouldProduceCriticalErrorWithError()
+ {
+ string expectedMessage = "Some critical error";
+ var result = Result.CriticalError(expectedMessage);
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.CriticalError);
+ actual.Errors.Single().Should().Be(expectedMessage);
+ }
+
+ [Fact]
+ public void ShouldProduceForbiddenWithError()
+ {
+ string expectedMessage = "You are forbidden";
+ var result = Result.Forbidden(expectedMessage);
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Forbidden);
+ actual.Errors.Single().Should().Be(expectedMessage);
+ }
+
+ [Fact]
+ public void ShouldProduceUnauthorizedWithError()
+ {
+ string expectedMessage = "You are unauthorized";
+ var result = Result.Unauthorized(expectedMessage);
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.Unauthorized);
+ actual.Errors.Single().Should().Be(expectedMessage);
+ }
+
+ [Fact]
+ public void ShouldProduceNoContentWithoutAnyContent()
+ {
+ var result = Result.NoContent();
+
+ var actual = result.Bind(val => Result.Success(val.ToString()));
+
+ actual.Status.Should().Be(ResultStatus.NoContent);
+ actual.Value.Should().BeNull();
+ actual.Errors.Should().BeEmpty();
+ actual.ValidationErrors.Should().BeEmpty();
+ }
+
+ [Fact]
+ public void ShouldPropagateErrorFromBindFunction()
+ {
+ int successValue = 123;
+ var result = Result.Success(successValue);
+ string expectedError = "Bind function failed";
+
+ var actual = result.Bind(val => Result.Error(expectedError));
+
+ actual.Status.Should().Be(ResultStatus.Error);
+ actual.Errors.Single().Should().Be(expectedError);
+ actual.Value.Should().BeNull();
+ }
+
+ [Fact]
+ public void ShouldHandleNestedResultsInBind()
+ {
+ int successValue = 123;
+ var result = Result.Success(successValue);
+
+ var actual = result.Bind(val =>
+ {
+ if (val > 1)
+ {
+ return Result.Success("Value is greater than 1");
+ }
+ else
+ {
+ return Result.Error("Value is less than or equal to 1");
+ }
+ });
+
+ actual.Status.Should().Be(ResultStatus.Ok);
+ actual.Value.Should().Be("Value is greater than 1");
+ }
+
+ [Fact]
+ public void ShouldHandleValidationErrorsFromBindFunction()
+ {
+ int successValue = 0;
+ var result = Result.Success(successValue);
+ var validationErrors = new List
+ {
+ new() { ErrorMessage = "Value must be greater than 1" }
+ };
+
+ var actual = result.Bind(val =>
+ {
+ if (val > 1)
+ {
+ return Result.Success("Valid value");
+ }
+ else
+ {
+ return Result.Invalid(validationErrors);
+ }
+ });
+
+ actual.Status.Should().Be(ResultStatus.Invalid);
+ actual.ValidationErrors.Should().BeEquivalentTo(validationErrors);
+ actual.Value.Should().BeNull();
+ }
+
+ private record Foo(string Bar);
+
+ private class FooDto
+ {
+ public string Bar { get; set; }
+
+ public FooDto(string bar)
+ {
+ Bar = bar;
+ }
+
+ public static Result CreateFromFooResult(Foo foo)
+ {
+ return Result.Success(new FooDto(foo.Bar));
+ }
+
+ public static Result CreateFromFooCreatedResult(Foo foo)
+ {
+ return Result.Created(new FooDto(foo.Bar));
+ }
+ }
+ }
+}
diff --git a/tests/Ardalis.Result.UnitTests/ResultBindAsync.cs b/tests/Ardalis.Result.UnitTests/ResultBindAsync.cs
new file mode 100644
index 0000000..a60fe70
--- /dev/null
+++ b/tests/Ardalis.Result.UnitTests/ResultBindAsync.cs
@@ -0,0 +1,802 @@
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Ardalis.Result.UnitTests
+{
+ public class ResultBindAsync
+ {
+ [Fact]
+ public async Task BindAsync_WithSuccessResultAndAsyncFunction_ReturnsSuccess()
+ {
+ int successValue = 123;
+ var result = Result.Success(successValue);
+ var expected = Result.Success(successValue.ToString());
+
+ var actual = await result.BindAsync(async val =>
+ {
+ await Task.Delay(1);
+ return Result.Success(val.ToString());
+ });
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public async Task BindAsync_WithAsyncResultAndAsyncFunction_ReturnsSuccess()
+ {
+ int successValue = 123;
+ var resultTask = Task.FromResult(Result.Success(successValue));
+ var expected = Result.Success(successValue.ToString());
+
+ var actual = await resultTask.BindAsync(async val =>
+ {
+ await Task.Delay(1);
+ return Result.Success(val.ToString());
+ });
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public async Task BindAsync_VoidAsyncResultToAsyncResult_ReturnsSuccess()
+ {
+ int successValue = 123;
+ var resultTask = Task.FromResult(Result.Success());
+ var expected = Result.Success(successValue);
+
+ var actual = await resultTask.BindAsync(async _ =>
+ {
+ await Task.Delay(1);
+ return Result.Success(successValue);
+ });
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public async Task BindAsync_VoidResultToAsyncResult_ReturnsSuccess()
+ {
+ int successValue = 123;
+ var result = Result.Success();
+ var expected = Result.Success(successValue);
+
+ var actual = await result.BindAsync(async _ =>
+ {
+ await Task.Delay(1);
+ return Result.Success(successValue);
+ });
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public async Task BindAsync_WithAsyncResultAndSyncFunction_ReturnsSuccess()
+ {
+ int successValue = 123;
+ var resultTask = Task.FromResult(Result.Success(successValue));
+ var expected = Result.Success(successValue.ToString());
+
+ var actual = await resultTask.BindAsync(val => Result.Success(val.ToString()));
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public async Task BindAsync_WithComplexTypeAndAsyncFunction_ReturnsSuccess()
+ {
+ var foo = new Foo("Bar");
+ var result = Result.Success(foo);
+ var expected = Result.Success(new FooDto(foo.Bar));
+
+ var actual = await result.BindAsync(async fooValue =>
+ {
+ await Task.Delay(1);
+ return Result.Success(new FooDto(fooValue.Bar));
+ });
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public async Task BindAsync_WithCreatedResultAndAsyncFunction_ReturnsCreated()
+ {
+ int createdValue = 123;
+ var result = Result.Created(createdValue);
+ var expected = Result.Created(createdValue.ToString());
+
+ var actual = await result.BindAsync(async val =>
+ {
+ await Task.Delay(1);
+ return Result.Created(val.ToString());
+ });
+
+ actual.Status.Should().Be(ResultStatus.Created);
+ actual.Value.Should().Be(expected.Value);
+ }
+
+ [Fact]
+ public async Task BindAsync_WithNotFoundResult_PropagatesNotFound()
+ {
+ var result = Result.NotFound();
+
+ var actual = await result.BindAsync(async val =>
+ {
+ await Task.Delay(1);
+ return Result.Success(val.ToString());
+ });
+
+ actual.Status.Should().Be(ResultStatus.NotFound);
+ actual.Value.Should().BeNull();
+ }
+
+ [Fact]
+ public async Task BindAsync_WithErrorInBindFunction_PropagatesError()
+ {
+ int successValue = 123;
+ var result = Result.Success(successValue);
+ string expectedError = "Bind function failed";
+
+ var actual = await result.BindAsync(async val =>
+ {
+ await Task.Delay(1);
+ return Result.Error(expectedError);
+ });
+
+ actual.Status.Should().Be(ResultStatus.Error);
+ actual.Errors.Single().Should().Be(expectedError);
+ actual.Value.Should().BeNull();
+ }
+
+ [Fact]
+ public async Task BindAsync_WithValidationErrorsInBindFunction_ReturnsInvalid()
+ {
+ int successValue = 1;
+ var result = Result.Success(successValue);
+ var validationErrors = new List
+ {
+ new() { ErrorMessage = "Value must be greater than 1" }
+ };
+
+ var actual = await result.BindAsync(async val =>
+ {
+ await Task.Delay(1);
+ if (val > 1)
+ {
+ return Result.Success("Valid value");
+ }
+ else
+ {
+ return Result.Invalid(validationErrors);
+ }
+ });
+
+ actual.Status.Should().Be(ResultStatus.Invalid);
+ actual.ValidationErrors.Should().BeEquivalentTo(validationErrors);
+ actual.Value.Should().BeNull();
+ }
+
+ [Fact]
+ public async Task BindAsync_WithUnauthorizedResult_PropagatesUnauthorized()
+ {
+ var result = Result.Unauthorized();
+
+ var actual = await result.BindAsync(async val =>
+ {
+ await Task.Delay(1);
+ return Result.Success(val.ToString());
+ });
+
+ actual.Status.Should().Be(ResultStatus.Unauthorized);
+ }
+
+ [Fact]
+ public async Task BindAsync_WithForbiddenResult_PropagatesForbidden()
+ {
+ var result = Result.Forbidden();
+
+ var actual = await result.BindAsync(async val =>
+ {
+ await Task.Delay(1);
+ return Result.Success(val.ToString());
+ });
+
+ actual.Status.Should().Be(ResultStatus.Forbidden);
+ }
+
+ [Fact]
+ public async Task BindAsync_AsyncResultWithErrorInBindFunction_ReturnsError()
+ {
+ var resultTask = Task.FromResult(Result.Success(123));
+
+ var actual = await resultTask.BindAsync(async val =>
+ {
+ await Task.Delay(1);
+ return Result.Error("Async error");
+ });
+
+ actual.Status.Should().Be(ResultStatus.Error);
+ actual.Errors.Single().Should().Be("Async error");
+ }
+
+ [Fact]
+ public async Task BindAsync_AsyncResultWithSyncBindFunction_ReturnsSuccess()
+ {
+ var resultTask = Task.FromResult(Result.Success(123));
+ var expected = Result.Success("123");
+
+ var actual = await resultTask.BindAsync(val => Result.Success(val.ToString()));
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public async Task BindAsync_VoidAsyncResultToVoidAsyncResult_ReturnsSuccess()
+ {
+ Task resultTask = Task.FromResult(Result.Success());
+ Result expected = Result.Success();
+
+ var actual = await resultTask.BindAsync(async r =>
+ {
+ await Task.Delay(1);
+ return Result.Success();
+ });
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public async Task BindAsync_VoidResultToAsyncResultWithValue_ReturnsSuccessWithValue()
+ {
+ int expectedValue = 123;
+ var result = Result.Success();
+
+ var actual = await result.BindAsync(async _ =>
+ {
+ await Task.Delay(1);
+ return Result.Success(expectedValue);
+ });
+
+ actual.Status.Should().Be(ResultStatus.Ok);
+ actual.Value.Should().Be(expectedValue);
+ }
+
+ [Fact]
+ public async Task BindAsync_AsyncResultWithValueToVoidAsyncResult_ReturnsSuccess()
+ {
+ var resultTask = Task.FromResult(Result