From 3bb960724f41d2d1eeb48d5dc0c7a603678c5a40 Mon Sep 17 00:00:00 2001 From: Ricardo Sanchez Date: Sun, 14 Aug 2022 18:06:00 +0200 Subject: [PATCH 1/2] feat: add .Bind operator to map results into a flattened Result --- README.md | 37 +- .../ResultWithValueTests.cs | 384 ++++++++++++++++++ .../ResultWithoutValueTests.cs | 378 +++++++++++++++++ .../Extensions/ResultExtensions.cs | 48 +++ src/FluentResults/Results/Result.cs | 291 ++++++++++++- 5 files changed, 1135 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ec018f0..f9edf67 100644 --- a/README.md +++ b/README.md @@ -327,7 +327,7 @@ Result.Ok(5).ToResult(v => v); Result.Fail("Failed").ToResult(); // converting a result to a result from type Result -Result.Ok().ToResult(); +Result.Ok().ToResult(); ``` ### Implicit conversion from T to success result ```Result``` @@ -337,6 +337,41 @@ string myString = "hello world"; Result result = myString; ``` +### Bind the result to another result + +Binding is a transformation that returns a `Result` | `Result`. +It only evaluates the transformation if the original result is successful. +The reasons of both `Result` will be merged into a new flattened `Result`. + +```csharp +// converting a result to a result which may fail +Result r = Result.Ok(8) + .Bind(v => v == 5 ? "five" : Result.Fail("It is not five")); + +// converting a failed result to a result, which can also fail, +// returns a result with the errors of the first result only, +// the transformation is not evaluated because the value of the first result is not available +Result r = Result.Fail("Not available") + .Bind(v => v == 5 ? "five" : Result.Fail("It is not five")); + +// converting a result with value to a Result via a transformation which may fail +Result.Ok(5).Bind(x => Result.OkIf(x == 6, "Number is not 6")); + +// converting a result without value into a Result +Result.Ok().Bind(() => Result.Ok(5)); + +// just running an action if the original result is sucessful. +Result r = Result.Ok().Bind(() => Result.Ok()); +``` + +The `Bind` has asynchronous overloads. + +```csharp +var result = await Result.Ok(5) + .Bind(int n => Task.FromResult(Result.Ok(n + 1).WithSuccess("Added one"))) + .Bind(int n => /* next continuation */); +``` + ### Set global factories for ISuccess/IError/IExceptionalError Within the FluentResults library in some scenarios an ISuccess, IError or IExceptionalError object is created. For example if the method ```Result.Fail("My Error")``` is called then internally an IError object is created. If you need to overwrite this behavior and create in this scenario a custom error class then you can set the error factory via the settings. The same extension points are also available for ISuccess and IExceptionalError. diff --git a/src/FluentResults.Test/ResultWithValueTests.cs b/src/FluentResults.Test/ResultWithValueTests.cs index 0ddfebd..ecab2f3 100644 --- a/src/FluentResults.Test/ResultWithValueTests.cs +++ b/src/FluentResults.Test/ResultWithValueTests.cs @@ -278,6 +278,390 @@ public void ToResult_ToAnotherValueTypeWithOkResultAndConverter_ReturnSuccessRes result.Value.Should().Be(4); } + public class BindMethod + { + [Fact] + public void Bind_ToAnotherValueTypeWithFailedResult_ReturnFailedResult() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = valueResult.Bind(Result.Ok); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWithFailedResult_ReturnFailedResultTask() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = await valueResult.Bind(x => Task.FromResult(Result.Ok(x))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWithFailedResult_ReturnFailedResultValueTask() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = await valueResult.Bind(x => new ValueTask>(Result.Ok(x))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public void Bind_ToResultWithFailedResult_ReturnFailedResult() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = valueResult.Bind(_ => Result.Ok()); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public async Task Bind_ToResultWithFailedResult_ReturnFailedResultTask() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = await valueResult.Bind(_ => Task.FromResult(Result.Ok())); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public async Task Bind_ToResultWithFailedResult_ReturnFailedResultValueTask() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = await valueResult.Bind(_ => new ValueTask(Result.Ok())); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public void Bind_ToAnotherValueTypeWithFailedResultAndFailedTransformation_ReturnFailedResult() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = valueResult.Bind(_ => Result.Fail("Irrelevant error")); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWithFailedResultAndFailedTransformation_ReturnFailedResultTask() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = await valueResult.Bind(_ => Task.FromResult(Result.Fail("Irrelevant error"))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWithFailedResultAndFailedTransformation_ReturnFailedResultValueTask() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = await valueResult.Bind(_ => Task.FromResult(Result.Fail("Irrelevant error"))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public void Bind_ToResultWithFailedResultAndFailedTransformation_ReturnFailedResult() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = valueResult.Bind(_ => Result.Fail("Irrelevant error")); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public async Task Bind_ToResultWithFailedResultAndFailedTransformation_ReturnFailedResultTask() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = await valueResult.Bind(_ => Task.FromResult(Result.Fail("Irrelevant error"))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public async Task Bind_ToResultWithFailedResultAndFailedTransformation_ReturnFailedResultValueTask() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = await valueResult.Bind(_ => new ValueTask(Result.Fail("Irrelevant error"))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public void Bind_ToAnotherValueTypeWhichIsSuccessful_ReturnsSuccessResult() + { + var valueResult = Result.Ok(1).WithSuccess("An int"); + + // Act + var result = valueResult.Bind(n => n == 1 + ? "One".ToResult().WithSuccess("It is one") + : Result.Fail("Only one accepted")); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Value.Should() + .Be("One"); + + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("An int", "It is one"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWhichIsSuccessful_ReturnsSuccessResultTask() + { + var valueResult = Result.Ok(1).WithSuccess("An int"); + + // Act + var result = await valueResult.Bind(n => Task.FromResult(n == 1 + ? "One".ToResult().WithSuccess("It is one") + : Result.Fail("Only one accepted"))); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Value.Should() + .Be("One"); + + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("An int", "It is one"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWhichIsSuccessful_ReturnsSuccessResultValueTask() + { + var valueResult = Result.Ok(1).WithSuccess("An int"); + + // Act + var result = await valueResult.Bind(n => new ValueTask>(n == 1 + ? "One".ToResult().WithSuccess("It is one") + : Result.Fail("Only one accepted"))); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Value.Should() + .Be("One"); + + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("An int", "It is one"); + } + + [Fact] + public void Bind_ToResultWhichIsSuccessful_ReturnsSuccessResult() + { + var valueResult = Result.Ok(1).WithSuccess("First number"); + + // Act + var result = valueResult.Bind(n => Result.OkIf(n == 1, "Irrelevant").WithSuccess("It is one")); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("First number", "It is one"); + } + + [Fact] + public async Task Bind_ToResultWhichIsSuccessful_ReturnsSuccessResultTask() + { + var valueResult = Result.Ok(1).WithSuccess("First number"); + + // Act + var result = await valueResult.Bind(n => Task.FromResult(Result.OkIf(n == 1, "Irrelevant").WithSuccess("It is one"))); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("First number", "It is one"); + } + + [Fact] + public async Task Bind_ToResultWhichIsSuccessful_ReturnsSuccessResultValueTask() + { + var valueResult = Result.Ok(1).WithSuccess("First number"); + + // Act + var result = await valueResult.Bind(n => new ValueTask>(Result.OkIf(n == 1, "Irrelevant").WithSuccess("It is one"))); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("First number", "It is one"); + } + + [Fact] + public void Bind_ToAnotherValueTypeWhichFailedTransformation_ReturnsFailedResult() + { + var valueResult = Result.Ok(5); + + // Act + var result = valueResult.Bind(n => Result.Fail("Only one accepted")); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWhichFailedTransformation_ReturnsFailedResultTask() + { + var valueResult = Result.Ok(5); + + // Act + var result = await valueResult.Bind(n => Task.FromResult(Result.Fail("Only one accepted"))); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWhichFailedTransformation_ReturnsFailedResultValueTask() + { + var valueResult = Result.Ok(5); + + // Act + var result = await valueResult.Bind(n => new ValueTask>(Result.Fail("Only one accepted"))); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + + [Fact] + public void Bind_ToResultWhichFailedTransformation_ReturnsFailedResult() + { + var valueResult = Result.Ok(5); + + // Act + var result = valueResult.Bind(n => Result.Fail("Only one accepted")); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + + [Fact] + public async Task Bind_ToResultWhichFailedTransformation_ReturnsFailedResultTask() + { + var valueResult = Result.Ok(5); + + // Act + var result = await valueResult.Bind(n => Task.FromResult(Result.Fail("Only one accepted"))); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + + [Fact] + public async Task Bind_ToResultWhichFailedTransformation_ReturnsFailedResultValueTask() + { + var valueResult = Result.Ok(5); + + // Act + var result = await valueResult.Bind(n => new ValueTask>(Result.Fail("Only one accepted"))); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + } + [Fact] public void ImplicitCastOperator_ReturnFailedResult() { diff --git a/src/FluentResults.Test/ResultWithoutValueTests.cs b/src/FluentResults.Test/ResultWithoutValueTests.cs index 94d6dbf..dcefa38 100644 --- a/src/FluentResults.Test/ResultWithoutValueTests.cs +++ b/src/FluentResults.Test/ResultWithoutValueTests.cs @@ -526,5 +526,383 @@ public void Can_deconstruct_non_generic_Fail_to_isSuccess_and_isFailed_and_error errors.Count.Should().Be(1); errors.FirstOrDefault().Should().Be(error); } + + public class BindMethod + { + [Fact] + public void Bind_ToAnotherValueTypeWithFailedResult_ReturnFailedResult() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = valueResult.Bind(() => Result.Ok(1)); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWithFailedResult_ReturnFailedResultTask() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = await valueResult.Bind(() => Task.FromResult(Result.Ok(1))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWithFailedResult_ReturnFailedResultValueTask() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = await valueResult.Bind(() => new ValueTask>(Result.Ok(1))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public void Bind_ToResultWithFailedResult_ReturnFailedResult() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = valueResult.Bind(Result.Ok); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public async Task Bind_ToResultWithFailedResult_ReturnFailedResultTask() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = await valueResult.Bind(() => Task.FromResult(Result.Ok())); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public async Task Bind_ToResultWithFailedResult_ReturnFailedResultValueTask() + { + var valueResult = Result.Fail("First error message"); + + // Act + var result = await valueResult.Bind(() => new ValueTask(Result.Ok())); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("First error message"); + } + + [Fact] + public void Bind_ToAnotherValueTypeWithFailedResultAndFailedTransformation_ReturnFailedResult() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = valueResult.Bind(() => Result.Fail("Irrelevant error")); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWithFailedResultAndFailedTransformation_ReturnFailedResultTask() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = await valueResult.Bind(() => Task.FromResult(Result.Fail("Irrelevant error"))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWithFailedResultAndFailedTransformation_ReturnFailedResultValueTask() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = await valueResult.Bind(() => Task.FromResult(Result.Fail("Irrelevant error"))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public void Bind_ToResultWithFailedResultAndFailedTransformation_ReturnFailedResult() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = valueResult.Bind(() => Result.Fail("Irrelevant error")); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public async Task Bind_ToResultWithFailedResultAndFailedTransformation_ReturnFailedResultTask() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = await valueResult.Bind(() => Task.FromResult(Result.Fail("Irrelevant error"))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public async Task Bind_ToResultWithFailedResultAndFailedTransformation_ReturnFailedResultValueTask() + { + var valueResult = Result.Fail("Original error message"); + + // Act + var result = await valueResult.Bind(() => new ValueTask(Result.Fail("Irrelevant error"))); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Select(e => e.Message) + .Should() + .BeEquivalentTo("Original error message"); + } + + [Fact] + public void Bind_ToAnotherValueTypeWhichIsSuccessful_ReturnsSuccessResult() + { + var valueResult = Result.Ok().WithSuccess("An int"); + + // Act + var result = valueResult.Bind(() => "One".ToResult().WithSuccess("It is one")); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Value.Should() + .Be("One"); + + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("An int", "It is one"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWhichIsSuccessful_ReturnsSuccessResultTask() + { + var valueResult = Result.Ok().WithSuccess("An int"); + + // Act + var result = await valueResult.Bind(() => Task.FromResult("One".ToResult().WithSuccess("It is one"))); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Value.Should() + .Be("One"); + + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("An int", "It is one"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWhichIsSuccessful_ReturnsSuccessResultValueTask() + { + var valueResult = Result.Ok().WithSuccess("An int"); + + // Act + var result = await valueResult.Bind(() => new ValueTask>("One".ToResult().WithSuccess("It is one"))); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Value.Should() + .Be("One"); + + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("An int", "It is one"); + } + + [Fact] + public void Bind_ToResultWhichIsSuccessful_ReturnsSuccessResult() + { + var valueResult = Result.Ok().WithSuccess("First number"); + + // Act + var result = valueResult.Bind(() => Result.Ok().WithSuccess("It is one")); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("First number", "It is one"); + } + + [Fact] + public async Task Bind_ToResultWhichIsSuccessful_ReturnsSuccessResultTask() + { + var valueResult = Result.Ok().WithSuccess("First number"); + + // Act + var result = await valueResult.Bind(() => Task.FromResult(Result.Ok().WithSuccess("It is one"))); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("First number", "It is one"); + } + + [Fact] + public async Task Bind_ToResultWhichIsSuccessful_ReturnsSuccessResultValueTask() + { + var valueResult = Result.Ok().WithSuccess("First number"); + + // Act + var result = await valueResult.Bind(() => new ValueTask>(Result.Ok().WithSuccess("It is one"))); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Successes.Select(s => s.Message) + .Should() + .BeEquivalentTo("First number", "It is one"); + } + + [Fact] + public void Bind_ToAnotherValueTypeWhichFailedTransformation_ReturnsFailedResult() + { + var valueResult = Result.Ok(); + + // Act + var result = valueResult.Bind(() => Result.Fail("Only one accepted")); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWhichFailedTransformation_ReturnsFailedResultTask() + { + var valueResult = Result.Ok(); + + // Act + var result = await valueResult.Bind(() => Task.FromResult(Result.Fail("Only one accepted"))); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + + [Fact] + public async Task Bind_ToAnotherValueTypeWhichFailedTransformation_ReturnsFailedResultValueTask() + { + var valueResult = Result.Ok(); + + // Act + var result = await valueResult.Bind(() => new ValueTask>(Result.Fail("Only one accepted"))); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + + [Fact] + public void Bind_ToResultWhichFailedTransformation_ReturnsFailedResult() + { + var valueResult = Result.Ok(); + + // Act + var result = valueResult.Bind(() => Result.Fail("Only one accepted")); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + + [Fact] + public async Task Bind_ToResultWhichFailedTransformation_ReturnsFailedResultTask() + { + var valueResult = Result.Ok(); + + // Act + var result = await valueResult.Bind(() => Task.FromResult(Result.Fail("Only one accepted"))); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + + [Fact] + public async Task Bind_ToResultWhichFailedTransformation_ReturnsFailedResultValueTask() + { + var valueResult = Result.Ok(); + + // Act + var result = await valueResult.Bind(() => new ValueTask>(Result.Fail("Only one accepted"))); + + // Assert + result.IsFailed.Should().BeTrue(); + + result.Errors.Select(s => s.Message) + .Should() + .BeEquivalentTo("Only one accepted"); + } + } } } \ No newline at end of file diff --git a/src/FluentResults/Extensions/ResultExtensions.cs b/src/FluentResults/Extensions/ResultExtensions.cs index 1f96686..f1d3673 100644 --- a/src/FluentResults/Extensions/ResultExtensions.cs +++ b/src/FluentResults/Extensions/ResultExtensions.cs @@ -52,5 +52,53 @@ public static async ValueTask> MapSuccesses(this ValueTask> Bind(this Task> resultTask, Func>> bind) + { + var result = await resultTask; + return await result.Bind(bind); + } + + public static async ValueTask> Bind(this ValueTask> resultTask, Func>> bind) + { + var result = await resultTask; + return await result.Bind(bind); + } + + public static async Task Bind(this Task> resultTask, Func> bind) + { + var result = await resultTask; + return await result.Bind(bind); + } + + public static async ValueTask Bind(this ValueTask> resultTask, Func> bind) + { + var result = await resultTask; + return await result.Bind(bind); + } + + public static async Task> Bind(this Task resultTask, Func>> bind) + { + var result = await resultTask; + return await result.Bind(bind); + } + + public static async ValueTask> Bind(this ValueTask resultTask, Func>> bind) + { + var result = await resultTask; + return await result.Bind(bind); + } + + public static async Task Bind(this Task resultTask, Func> bind) + { + var result = await resultTask; + return await result.Bind(bind); + } + + public static async ValueTask Bind(this ValueTask resultTask, Func> bind) + { + var result = await resultTask; + return await result.Bind(bind); + } } } diff --git a/src/FluentResults/Results/Result.cs b/src/FluentResults/Results/Result.cs index 492f9aa..febc136 100644 --- a/src/FluentResults/Results/Result.cs +++ b/src/FluentResults/Results/Result.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Collections.Generic; +using System.Threading.Tasks; // ReSharper disable once CheckNamespace namespace FluentResults @@ -19,7 +20,7 @@ public Result MapErrors(Func errorMapper) { if (IsSuccess) return this; - + return new Result() .WithErrors(Errors.Select(errorMapper)) .WithSuccesses(Successes); @@ -43,6 +44,147 @@ public Result ToResult(TNewValue newValue = default) .WithValue(IsFailed ? default : newValue) .WithReasons(Reasons); } + + /// + /// Convert result to result with value that may fail. + /// + /// + /// + /// var bakeryDtoResult = result.Bind(GetWhichMayFail); + /// + /// + /// Transformation that may fail. + public Result Bind(Func> bind) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = bind(); + result.WithValue(converted.ValueOrDefault); + result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + /// Convert result to result with value that may fail asynchronously. + /// + /// + /// + /// var bakeryDtoResult = result.Bind(GetWhichMayFail); + /// + /// + /// Transformation that may fail. + public async Task> Bind(Func>> bind) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = await bind(); + result.WithValue(converted.ValueOrDefault); + result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + /// Convert result to result with value that may fail asynchronously. + /// + /// + /// + /// var bakeryDtoResult = result.Bind(GetWhichMayFail); + /// + /// + /// Transformation that may fail. + public async ValueTask> Bind(Func>> bind) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = await bind(); + result.WithValue(converted.ValueOrDefault); + result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + /// Execute an action which returns a . + /// + /// + /// + /// var done = result.Bind(ActionWhichMayFail); + /// + /// + /// Action that may fail. + public Result Bind(Func action) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = action(); + result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + /// Execute an action which returns a asynchronously. + /// + /// + /// + /// var done = result.Bind(ActionWhichMayFail); + /// + /// + /// Action that may fail. + public async Task Bind(Func> action) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = await action(); + result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + /// Execute an action which returns a asynchronously. + /// + /// + /// + /// var done = result.Bind(ActionWhichMayFail); + /// + /// + /// Action that may fail. + public async ValueTask Bind(Func> action) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = await action(); + result.WithReasons(converted.Reasons); + } + + return result; + } } public interface IResult : IResultBase @@ -148,6 +290,150 @@ public Result ToResult(Func valueConver .WithReasons(Reasons); } + /// + /// Convert result with value to result with another value that may fail. + /// + /// + /// + /// var bakeryDtoResult = result + /// .Bind(GetWhichMayFail) + /// .Bind(ProcessWhichMayFail) + /// .Bind(FormattingWhichMayFail); + /// + /// + /// Transformation that may fail. + public Result Bind(Func> bind) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = bind(Value); + result.WithValue(converted.ValueOrDefault); + result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + /// Convert result with value to result with another value that may fail asynchronously. + /// + /// + /// + /// var bakeryDtoResult = await result.Bind(GetWhichMayFail); + /// + /// + /// Transformation that may fail. + public async Task> Bind(Func>> bind) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = await bind(Value); + result.WithValue(converted.ValueOrDefault); + result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + /// Convert result with value to result with another value that may fail asynchronously. + /// + /// + /// + /// var bakeryDtoResult = await result.Bind(GetWhichMayFail); + /// + /// + /// Transformation that may fail. + public async ValueTask> Bind(Func>> bind) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = await bind(Value); + result.WithValue(converted.ValueOrDefault); + result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + /// Execute an action which returns a . + /// + /// + /// + /// var done = result.Bind(ActionWhichMayFail); + /// + /// + /// Action that may fail. + public Result Bind(Func action) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = action(Value); + result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + /// Execute an action which returns a asynchronously. + /// + /// + /// + /// var done = await result.Bind(ActionWhichMayFail); + /// + /// + /// Action that may fail. + public async Task Bind(Func> action) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = await action(Value); + result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + /// Execute an action which returns a asynchronously. + /// + /// + /// + /// var done = await result.Bind(ActionWhichMayFail); + /// + /// + /// Action that may fail. + public async ValueTask Bind(Func> action) + { + var result = new Result(); + result.WithReasons(Reasons); + + if (IsSuccess) + { + var converted = await action(Value); + result.WithReasons(converted.Reasons); + } + + return result; + } + public override string ToString() { var baseString = base.ToString(); @@ -180,6 +466,7 @@ public void Deconstruct(out bool isSuccess, out bool isFailed, out TValue value) isFailed = IsFailed; value = IsSuccess ? Value : default; } + /// /// Deconstruct Result /// @@ -194,7 +481,7 @@ public void Deconstruct(out bool isSuccess, out bool isFailed, out TValue value, value = IsSuccess ? Value : default; errors = IsFailed ? Errors : default; } - + private void ThrowIfFailed() { if (IsFailed) From 8d64f3bea5b080c675d69284189e01aac1a9ea7b Mon Sep 17 00:00:00 2001 From: Michael Altmann Date: Sat, 20 Aug 2022 10:34:43 +0200 Subject: [PATCH 2/2] Add some missing extensions --- src/FluentResults.Test/Playground.cs | 40 ++++++++++++++----- .../Extensions/ResultExtensions.cs | 28 ++++++++++++- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/FluentResults.Test/Playground.cs b/src/FluentResults.Test/Playground.cs index 7ef2eb5..d892a6b 100644 --- a/src/FluentResults.Test/Playground.cs +++ b/src/FluentResults.Test/Playground.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using FluentResults.Extensions; namespace FluentResults.Test { @@ -21,31 +23,30 @@ public void Simple() var voidResult = Result.Ok(); voidResult = Result.Ok() - .WithSuccess("This is a success") - .WithSuccess("This is a second success"); + .WithSuccess("This is a success") + .WithSuccess("This is a second success"); voidResult = Result.Fail("First error"); voidResult = Result.Fail(new Error("First error")); voidResult = Result.Fail("First error") - .WithError("second error") - .WithError(new CustomError().CausedBy(new InvalidCastException())); - + .WithError("second error") + .WithError(new CustomError().CausedBy(new InvalidCastException())); var valueResult = Result.Ok(default) - .WithSuccess("first success") - .WithValue(3); + .WithSuccess("first success") + .WithValue(3); valueResult = Result.Ok(3); valueResult = Result.Ok(default) - .WithValue(3); + .WithValue(3); valueResult = Result.Fail("First error"); valueResult = Result.Fail(new Error("first error")) - .WithError("second error"); + .WithError("second error"); IEnumerable results = new List(); Result mergedResult = results.Merge(); @@ -89,5 +90,26 @@ public void LogTest() var result1 = Result.Ok(); result1 = result1.Log(); } + + public async Task BindTests() + { + var r = Result.Ok(1); + + var r1 = await r.Bind(v => GetVResult()) + .Bind(v => GetVResultAsync()) + .Bind(v => GetVResult()) + .Bind(v => GetVResultAsync()) + ; + + var r2 = await r.Bind(v => GetVResultAsync()); + } + + private Result GetResult() => Result.Ok(); + + private Result GetVResult() => Result.Ok(1); + + private Task GetResultAsync() => Task.FromResult(GetResult()); + + private Task> GetVResultAsync() => Task.FromResult(GetVResult()); } } \ No newline at end of file diff --git a/src/FluentResults/Extensions/ResultExtensions.cs b/src/FluentResults/Extensions/ResultExtensions.cs index f1d3673..5326943 100644 --- a/src/FluentResults/Extensions/ResultExtensions.cs +++ b/src/FluentResults/Extensions/ResultExtensions.cs @@ -64,13 +64,37 @@ public static async ValueTask> Bind(this ValueTask> Bind(this Task> resultTask, Func> bind) + { + var result = await resultTask; + return result.Bind(bind); + } + + public static async ValueTask> Bind(this ValueTask> resultTask, Func> bind) + { + var result = await resultTask; + return result.Bind(bind); + } + public static async Task Bind(this Task> resultTask, Func> bind) { var result = await resultTask; return await result.Bind(bind); } - + + public static async Task Bind(this Task> resultTask, Func bind) + { + var result = await resultTask; + return result.Bind(bind); + } + + public static async ValueTask Bind(this ValueTask> resultTask, Func bind) + { + var result = await resultTask; + return result.Bind(bind); + } + public static async ValueTask Bind(this ValueTask> resultTask, Func> bind) { var result = await resultTask;