From 567a22fb2c7b9b5a651a1829363cc8fbc2e140dc Mon Sep 17 00:00:00 2001 From: DrPepperBianco <11694093+DrPepperBianco@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:24:41 +0200 Subject: [PATCH 1/5] Added extension methods for IResultBase and IResult Theses changes allow to do fluent syntax when working with the interface types instead of the explicit types. The ceveat is, that because these methods always create a new Result object, we might lose the covariance here. --- .../Extensions/ResultExtensions.cs | 409 ++++++++++++++++++ 1 file changed, 409 insertions(+) diff --git a/src/FluentResults/Extensions/ResultExtensions.cs b/src/FluentResults/Extensions/ResultExtensions.cs index f11dfbe..0d4fcc4 100644 --- a/src/FluentResults/Extensions/ResultExtensions.cs +++ b/src/FluentResults/Extensions/ResultExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; namespace FluentResults.Extensions @@ -148,5 +149,413 @@ public static async Task> ToResult(this ValueTask var result = await resultTask; return result.ToResult(value); } + + #region Interfaces + + public static async Task MapErrors(this Task resultTask, Func errorMapper) + { + var result = await resultTask; + return result.MapErrors(errorMapper); + } + + public static async ValueTask MapErrors(this ValueTask resultTask, Func errorMapper) + { + var result = await resultTask; + return result.MapErrors(errorMapper); + } + + public static async Task> MapErrors(this Task> resultTask, Func errorMapper) + { + var result = await resultTask; + return result.MapErrors(errorMapper); + } + + public static async ValueTask> MapErrors(this ValueTask> resultTask, Func errorMapper) + { + var result = await resultTask; + return result.MapErrors(errorMapper); + } + + public static async Task MapSuccesses(this Task resultTask, Func errorMapper) + { + var result = await resultTask; + return result.MapSuccesses(errorMapper); + } + + public static async ValueTask MapSuccesses(this ValueTask resultTask, Func errorMapper) + { + var result = await resultTask; + return result.MapSuccesses(errorMapper); + } + + public static async Task> MapSuccesses(this Task> resultTask, Func errorMapper) + { + var result = await resultTask; + return result.MapSuccesses(errorMapper); + } + + public static async ValueTask> MapSuccesses(this ValueTask> resultTask, Func errorMapper) + { + var result = await resultTask; + return result.MapSuccesses(errorMapper); + } + + 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 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; + 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> Map(this Task> resultTask, Func valueConverter) + { + var result = await resultTask; + return result.Map(valueConverter); + } + + public static async Task> Map(this ValueTask> resultTask, Func valueConverter) + { + var result = await resultTask; + return result.Map(valueConverter); + } + + public static async Task> ToResult(this Task resultTask, TValue value) + { + var result = await resultTask; + return result.ToResult(value); + } + + public static async Task> ToResult(this ValueTask resultTask, TValue value) + { + var result = await resultTask; + return result.ToResult(value); + } + + #endregion + + + #region Erweiterung: Member von Result für Interface IResultBase + + /// + public static IResultBase MapErrors(this IResultBase result, Func errorMapper) => + result.IsSuccess + ? result + : new Result() + .WithErrors(result.Errors.Select(errorMapper)) + .WithSuccesses(result.Successes); + + /// + public static IResultBase MapSuccesses(this IResultBase result, Func successMapper) => + new Result() + .WithErrors(result.Errors) + .WithSuccesses(result.Successes.Select(successMapper)); + + /// + public static IResult ToResult(this IResultBase result, TNewValue newValue = default) => + new Result() + .WithValue(result.IsFailed ? default : newValue) + .WithReasons(result.Reasons); + + /// + public static IResult Bind(this IResultBase result, Func> bind) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = bind(); + out_result.WithValue(converted.ValueOrDefault); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + /// + public static async Task> Bind(this IResultBase result, Func>> bind) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = await bind(); + out_result.WithValue(converted.ValueOrDefault); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + /// + public static async ValueTask> Bind(this IResultBase result, Func>> bind) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = await bind(); + out_result.WithValue(converted.ValueOrDefault); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + /// + public static IResultBase Bind(this IResultBase result, Func action) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = action(); + out_result.WithReasons(converted.Reasons); + } + + return result; + } + + /// + public static async Task Bind(this IResultBase result, Func> action) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = await action(); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + /// + public static async ValueTask Bind(this IResultBase result, Func> action) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = await action(); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + #endregion + + + #region Erweiterung: Member von Result für Interface IResultBase + + /// + public static IResult WithValue(this IResult result, TValue value) => + // ℹ️ You would assume immutability on results, + // therefor we first create a copy! + new Result() + .WithReasons(result.Reasons) + .WithValue(value); + + + /// + public static IResult MapErrors(this IResult result, Func errorMapper) => + result.IsSuccess + ? result + : new Result() + .WithErrors(result.Errors.Select(errorMapper)) + .WithSuccesses(result.Successes); + + /// + public static IResult MapSuccesses(this IResult result, Func successMapper) => + new Result() + .WithValue(result.ValueOrDefault) + .WithErrors(result.Errors) + .WithSuccesses(result.Successes.Select(successMapper)); + + /// + public static IResultBase ToResult(this IResult result) => + new Result() + .WithReasons(result.Reasons); + + /// + public static Result ToResult(this IResult result, Func valueConverter = null) => + Map(result, valueConverter); + + /// + public static Result Map(this IResult result, Func mapLogic) + { + if(result.IsSuccess && mapLogic == null) + throw new ArgumentException("If result is success then valueConverter should not be null"); + + return new Result() + .WithValue(result.IsFailed ? default : mapLogic(result.Value)) + .WithReasons(result.Reasons); + } + + /// + public static Result Bind(this IResult result, Func> bind) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = bind(result.Value); + out_result.WithValue(converted.ValueOrDefault); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + /// + public static async Task> Bind(this IResult result, Func>> bind) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = await bind(result.Value); + out_result.WithValue(converted.ValueOrDefault); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + /// + public static async ValueTask> Bind(this IResult result, Func>> bind) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = await bind(result.Value); + out_result.WithValue(converted.ValueOrDefault); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + /// + public static IResultBase Bind(this IResult result, Func action) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = action(result.Value); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + /// + public static async Task Bind(this IResult result, Func> action) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = await action(result.Value); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + /// + public static async ValueTask Bind(this IResult result, Func> action) + { + var out_result = new Result(); + out_result.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = await action(result.Value); + out_result.WithReasons(converted.Reasons); + } + + return out_result; + } + + #endregion } } From 5589605759e0b411f174fd31ec47c869479bca62 Mon Sep 17 00:00:00 2001 From: DrPepperBianco <11694093+DrPepperBianco@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:48:13 +0200 Subject: [PATCH 2/5] IResultTExtensions and refactor of ResultExtensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ResultExtensions only contains extensions for Task<…> and Value<…> objects. All Extensions for IResult are now in IResultTExtensions. IResultTExtensions is directly in Namespace FluentResults. New method "MapReasons()". Makes it easier to implement MapErros and MapSuccesses. The overloads of Bind implemented by one method that is called from the other overloads. There are two variants. Bind() that returns Result without value and Bind() that returns Result with value. Return value is always directly Result or Result except for "MapErrors", because MapErrors can return the IResult argument directly. All other methods always create a new object as return value. --- .../Extensions/IResultTExtensions.cs | 142 +++++++++++++ .../Extensions/ResultExtensions.cs | 187 ++++-------------- 2 files changed, 182 insertions(+), 147 deletions(-) create mode 100644 src/FluentResults/Extensions/IResultTExtensions.cs diff --git a/src/FluentResults/Extensions/IResultTExtensions.cs b/src/FluentResults/Extensions/IResultTExtensions.cs new file mode 100644 index 0000000..b85501a --- /dev/null +++ b/src/FluentResults/Extensions/IResultTExtensions.cs @@ -0,0 +1,142 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; + +// ReSharper disable once CheckNamespace +namespace FluentResults +{ + /// + /// Extensions methods for + /// + public static class IResultTExtensions + { + /// + /// Map all reasons of the result via reasonMapper + /// + /// + /// If TReason is different from IReason the mapper is only to applied to + /// reasons of the appropriate type. + /// + public static Result MapReasons(this IResult result, Func reasonMapper) + where TReason : IReason => + // Create new Result object + new Result() + // Set value, if any + .WithValue(result.ValueOrDefault) + // Set and map all reasons + .WithReasons( + result.Reasons.Select(reason => + reason is TReason tReason + ? reasonMapper(tReason) + : reason)); + + + /// + public static IResult MapErrors(this IResult result, Func errorMapper) => + result.IsSuccess + ? result + : result.MapReasons(errorMapper); + + /// + public static Result MapSuccesses(this IResult result, Func successMapper) => + result.MapReasons(successMapper); + + /// + public static Result ToResult(this IResult result) => + new Result() + .WithReasons(result.Reasons); + + /// + public static Result ToResult(this IResult result, Func valueConverter = null) => + Map(result, valueConverter); + + /// + public static Result Map(this IResult result, Func mapLogic) + { + if(result.IsSuccess && mapLogic == null) + throw new ArgumentException("If result is success then valueConverter should not be null"); + + return new Result() + .WithValue(result.IsFailed ? default : mapLogic(result.Value)) + .WithReasons(result.Reasons); + } + + + #region Variants of Bind(), that return result with value + + /// + public static Result Bind(this IResult result, Func> bind) => + Bind(result, new Func>>(value => new ValueTask>(bind(value)))) + .GetAwaiter().GetResult(); + + /// + public static Task> Bind(this IResult result, Func>> bind) => + Bind(result, new Func>>(async (value) => await bind(value))); + + /// + public static Task> Bind(this IResult result, Func>> bind) => + Bind(result, new Func>>(async (value) => await bind(value))) + .AsTask(); + + /// + public static ValueTask> Bind(this IResult result, Func>> bind) => + Bind(result, new Func>>(async (value) => await bind(value))); + + /// + public static async ValueTask> Bind(this IResult result, Func>> bind) + { + var outResult = new Result(); + outResult.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = await bind(result.Value); + outResult.WithValue(converted.ValueOrDefault); + outResult.WithReasons(converted.Reasons); + } + + return outResult; + } + + #endregion + + + #region Variants of Bind(), that return result without value + + /// + public static Result Bind(this IResult result, Func action) => + Bind(result, new Func>(value => new ValueTask(action(value)))) + .GetAwaiter().GetResult(); + + /// + public static Task Bind(this IResult result, Func> action) => + Bind(result, new Func>(async (value) => await action(value))); + + /// + public static Task Bind(this IResult result, Func> action) => + Bind(result, new Func>(async (value) => await action(value))) + .AsTask(); + + /// + public static ValueTask Bind(this IResult result, Func> action) => + Bind(result, new Func>(async (value) => await action(value))); + + /// + public static async ValueTask Bind(this IResult result, Func> action) + { + var outResult = new Result(); + outResult.WithReasons(result.Reasons); + + if(result.IsSuccess) + { + var converted = await action(result.Value); + outResult.WithReasons(converted.Reasons); + } + + return outResult; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/FluentResults/Extensions/ResultExtensions.cs b/src/FluentResults/Extensions/ResultExtensions.cs index 0d4fcc4..138a2bc 100644 --- a/src/FluentResults/Extensions/ResultExtensions.cs +++ b/src/FluentResults/Extensions/ResultExtensions.cs @@ -6,6 +6,8 @@ namespace FluentResults.Extensions { public static class ResultExtensions { + #region Extensions for Task, ValueTask, Task>, and ValueTask> + public static async Task MapErrors(this Task resultTask, Func errorMapper) { var result = await resultTask; @@ -150,8 +152,12 @@ public static async Task> ToResult(this ValueTask return result.ToResult(value); } - #region Interfaces + #endregion + + +#region Extensions for Task, ValueTask, Task>, and ValueTask> +#if false public static async Task MapErrors(this Task resultTask, Func errorMapper) { var result = await resultTask; @@ -163,6 +169,7 @@ public static async ValueTask MapErrors(this ValueTask var result = await resultTask; return result.MapErrors(errorMapper); } +#endif public static async Task> MapErrors(this Task> resultTask, Func errorMapper) { @@ -176,6 +183,7 @@ public static async ValueTask> MapErrors(this ValueTask return result.MapErrors(errorMapper); } +#if false public static async Task MapSuccesses(this Task resultTask, Func errorMapper) { var result = await resultTask; @@ -187,6 +195,7 @@ public static async ValueTask MapSuccesses(this ValueTask> MapSuccesses(this Task> resultTask, Func errorMapper) { @@ -248,6 +257,7 @@ public static async ValueTask Bind(this ValueTask> Bind(this Task resultTask, Func>> bind) { var result = await resultTask; @@ -271,6 +281,7 @@ public static async ValueTask Bind(this ValueTask resu var result = await resultTask; return await result.Bind(bind); } +#endif public static async Task> Map(this Task> resultTask, Func valueConverter) { @@ -284,23 +295,45 @@ public static async Task> Map(this Valu return result.Map(valueConverter); } - public static async Task> ToResult(this Task resultTask, TValue value) + #endregion + + #region Convert (Value)Task of IResultBase into Task of Result + + public static async Task ToResult(this Task resultTask) { var result = await resultTask; - return result.ToResult(value); + return new Result() + .WithReasons(result.Reasons); } - public static async Task> ToResult(this ValueTask resultTask, TValue value) + public static async ValueTask ToResult(this ValueTask resultTask) { var result = await resultTask; - return result.ToResult(value); + return new Result() + .WithReasons(result.Reasons); + } + + public static async Task> ToResult(this Task resultTask, TValue value) + { + var result = await resultTask; + return new Result() + .WithValue(value) + .WithReasons(result.Reasons); + } + + public static async ValueTask> ToResult(this ValueTask resultTask, TValue value) + { + var result = await resultTask; + return new Result() + .WithValue(value) + .WithReasons(result.Reasons); } #endregion #region Erweiterung: Member von Result für Interface IResultBase - +#if false /// public static IResultBase MapErrors(this IResultBase result, Func errorMapper) => result.IsSuccess @@ -413,149 +446,9 @@ public static async ValueTask Bind(this IResultBase result, Func - public static IResult WithValue(this IResult result, TValue value) => - // ℹ️ You would assume immutability on results, - // therefor we first create a copy! - new Result() - .WithReasons(result.Reasons) - .WithValue(value); - - - /// - public static IResult MapErrors(this IResult result, Func errorMapper) => - result.IsSuccess - ? result - : new Result() - .WithErrors(result.Errors.Select(errorMapper)) - .WithSuccesses(result.Successes); - - /// - public static IResult MapSuccesses(this IResult result, Func successMapper) => - new Result() - .WithValue(result.ValueOrDefault) - .WithErrors(result.Errors) - .WithSuccesses(result.Successes.Select(successMapper)); - - /// - public static IResultBase ToResult(this IResult result) => - new Result() - .WithReasons(result.Reasons); - - /// - public static Result ToResult(this IResult result, Func valueConverter = null) => - Map(result, valueConverter); - - /// - public static Result Map(this IResult result, Func mapLogic) - { - if(result.IsSuccess && mapLogic == null) - throw new ArgumentException("If result is success then valueConverter should not be null"); - - return new Result() - .WithValue(result.IsFailed ? default : mapLogic(result.Value)) - .WithReasons(result.Reasons); - } - - /// - public static Result Bind(this IResult result, Func> bind) - { - var out_result = new Result(); - out_result.WithReasons(result.Reasons); - - if(result.IsSuccess) - { - var converted = bind(result.Value); - out_result.WithValue(converted.ValueOrDefault); - out_result.WithReasons(converted.Reasons); - } - - return out_result; - } - - /// - public static async Task> Bind(this IResult result, Func>> bind) - { - var out_result = new Result(); - out_result.WithReasons(result.Reasons); - - if(result.IsSuccess) - { - var converted = await bind(result.Value); - out_result.WithValue(converted.ValueOrDefault); - out_result.WithReasons(converted.Reasons); - } - - return out_result; - } - - /// - public static async ValueTask> Bind(this IResult result, Func>> bind) - { - var out_result = new Result(); - out_result.WithReasons(result.Reasons); - - if(result.IsSuccess) - { - var converted = await bind(result.Value); - out_result.WithValue(converted.ValueOrDefault); - out_result.WithReasons(converted.Reasons); - } - - return out_result; - } - - /// - public static IResultBase Bind(this IResult result, Func action) - { - var out_result = new Result(); - out_result.WithReasons(result.Reasons); - - if(result.IsSuccess) - { - var converted = action(result.Value); - out_result.WithReasons(converted.Reasons); - } - - return out_result; - } - - /// - public static async Task Bind(this IResult result, Func> action) - { - var out_result = new Result(); - out_result.WithReasons(result.Reasons); - - if(result.IsSuccess) - { - var converted = await action(result.Value); - out_result.WithReasons(converted.Reasons); - } - - return out_result; - } - - /// - public static async ValueTask Bind(this IResult result, Func> action) - { - var out_result = new Result(); - out_result.WithReasons(result.Reasons); - - if(result.IsSuccess) - { - var converted = await action(result.Value); - out_result.WithReasons(converted.Reasons); - } - - return out_result; - } - - #endregion } } From 75b4a226aa304a55857970a739a5a98b970f9e45 Mon Sep 17 00:00:00 2001 From: DrPepperBianco <11694093+DrPepperBianco@users.noreply.github.com> Date: Wed, 23 Aug 2023 22:26:49 +0200 Subject: [PATCH 3/5] Added Some Tests for IResultTExtensions --- .../IResultTExtensionsTests.cs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/FluentResults.Test/IResultTExtensionsTests.cs diff --git a/src/FluentResults.Test/IResultTExtensionsTests.cs b/src/FluentResults.Test/IResultTExtensionsTests.cs new file mode 100644 index 0000000..5b1641a --- /dev/null +++ b/src/FluentResults.Test/IResultTExtensionsTests.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using FluentAssertions; +using FluentResults.Test.Mocks; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace FluentResults.Test +{ + public class IResultTExtensionsTests + { + private void AssertEqual(IResult actual, IResult expected) + { + // Assert + actual.IsFailed.Should().Be(expected.IsFailed); + actual.IsSuccess.Should().Be(expected.IsSuccess); + actual.ValueOrDefault.Should().Be(expected.ValueOrDefault); + + actual.Reasons.Should().BeEquivalentTo(expected.Reasons); + actual.Errors.Should().BeEquivalentTo(expected.Errors); + actual.Successes.Should().BeEquivalentTo(expected.Successes); + } + + [Fact] + public void Ok_MapReason_with_IError_NoChange() + { + // Act + IResult result = Result.Ok("FooBar"); + IResult actual = result.MapReasons((IError error) => new Error("\"" + error.Message + "\"")); + + // Assert + AssertEqual(actual, result); + } + + [Fact] + public void Fail_MapReason_with_IError_ShouldChange() + { + // Act + IResult result = Result.Fail("FooBar"); + IResult actual = result.MapReasons((IError error) => new Error("\"" + error.Message + "\"")); + + // Assert + var expected = result; + actual.IsFailed.Should().Be(expected.IsFailed); + actual.IsSuccess.Should().Be(expected.IsSuccess); + actual.ValueOrDefault.Should().Be(expected.ValueOrDefault); + + actual.Successes.Should().BeEquivalentTo(expected.Successes); + actual.Reasons.Should().NotBeEquivalentTo(expected.Reasons); + actual.Errors.Should().NotBeEquivalentTo(expected.Errors); + + actual.Errors.Count.Should().Be(1); + actual.Errors[0].Message.Should().Be("\"FooBar\""); + } + + [Fact] + public void Ok_Bind_Success() + { + // Act + IResult result = Result.Ok("4815162342"); + IResult actual = result.Bind(s => Result.Try(() => long.Parse(s))); + + // Assert + actual.IsSuccess.Should().Be(true); + actual.IsFailed.Should().Be(false); + actual.Errors.Should().BeEmpty(); + actual.Reasons.Should().BeEmpty(); + actual.Successes.Should().BeEmpty(); + actual.ValueOrDefault.Should().Be(4815162342L); + } + + [Fact] + public void Ok_Bind_Failure() + { + // Act + bool delegateHasBeenCalled = false; + IResult result = Result.Ok("4815162342").WithError("FooBar"); + IResult actual = result.Bind(s => + { + delegateHasBeenCalled = true; + return Result.Try(() => long.Parse(s)); + }); + + // Assert + delegateHasBeenCalled.Should().Be(false, "Bind of failed result should be called"); + actual.IsSuccess.Should().Be(false); + actual.IsFailed.Should().Be(true); + actual.Errors.Should().NotBeEmpty(); + actual.Errors.Count.Should().Be(1); + actual.Reasons.Should().NotBeEmpty(); + actual.Reasons.Count.Should().Be(1); + actual.Successes.Should().BeEmpty(); + actual.ValueOrDefault.Should().Be(default); + } + } +} From 4bc152a2afcf11c38f4463483097e6810a87fd4a Mon Sep 17 00:00:00 2001 From: DrPepperBianco <11694093+DrPepperBianco@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:53:08 +0200 Subject: [PATCH 4/5] rename IResultTExtensions to ResulTValueExtensions seems a better name, also avoids letting a class start with an I. --- ...tTExtensionsTests.cs => ResultTValueExtensionsTests.cs} | 2 +- .../{IResultTExtensions.cs => ResultTValueExtensions.cs} | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) rename src/FluentResults.Test/{IResultTExtensionsTests.cs => ResultTValueExtensionsTests.cs} (98%) rename src/FluentResults/Extensions/{IResultTExtensions.cs => ResultTValueExtensions.cs} (96%) diff --git a/src/FluentResults.Test/IResultTExtensionsTests.cs b/src/FluentResults.Test/ResultTValueExtensionsTests.cs similarity index 98% rename from src/FluentResults.Test/IResultTExtensionsTests.cs rename to src/FluentResults.Test/ResultTValueExtensionsTests.cs index 5b1641a..a45fe6d 100644 --- a/src/FluentResults.Test/IResultTExtensionsTests.cs +++ b/src/FluentResults.Test/ResultTValueExtensionsTests.cs @@ -11,7 +11,7 @@ namespace FluentResults.Test { - public class IResultTExtensionsTests + public class ResultTValueExtensionsTests { private void AssertEqual(IResult actual, IResult expected) { diff --git a/src/FluentResults/Extensions/IResultTExtensions.cs b/src/FluentResults/Extensions/ResultTValueExtensions.cs similarity index 96% rename from src/FluentResults/Extensions/IResultTExtensions.cs rename to src/FluentResults/Extensions/ResultTValueExtensions.cs index b85501a..d394abb 100644 --- a/src/FluentResults/Extensions/IResultTExtensions.cs +++ b/src/FluentResults/Extensions/ResultTValueExtensions.cs @@ -9,7 +9,12 @@ namespace FluentResults /// /// Extensions methods for /// - public static class IResultTExtensions + /// + /// Adds methods that are defined in + /// so that they are also + /// available in . + /// + public static class ResultTValueExtensions { /// /// Map all reasons of the result via reasonMapper From fed8be5be5c76aa7dd7f906cb717701bfe8054d5 Mon Sep 17 00:00:00 2001 From: DrPepperBianco <11694093+DrPepperBianco@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:53:32 +0200 Subject: [PATCH 5/5] ResultBaseExtensions Adds methods, that are else defined in ResultBase, so that they are also available for IResultBase. --- .../Extensions/ResultBaseExtensions.cs | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 src/FluentResults/Extensions/ResultBaseExtensions.cs diff --git a/src/FluentResults/Extensions/ResultBaseExtensions.cs b/src/FluentResults/Extensions/ResultBaseExtensions.cs new file mode 100644 index 0000000..5693da1 --- /dev/null +++ b/src/FluentResults/Extensions/ResultBaseExtensions.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FluentResults +{ + /// + /// Extensions methods for IResultBase + /// + /// + /// Adds methods, that are else defined in ResultBase, so that + /// they are also available for IResultBase. + /// + public static class ResultBaseExtensions + { + #region Methods from ResultBase + + /// + public static bool HasError(this IResultBase resultBase) where TError : IError + { + return HasError(resultBase, out _); + } + + /// + public static bool HasError(this IResultBase resultBase, out IEnumerable result) where TError : IError + { + return HasError(resultBase, e => true, out result); + } + + /// + public static bool HasError(this IResultBase resultBase, Func predicate) where TError : IError + { + return HasError(resultBase, predicate, out _); + } + + /// + public static bool HasError(this IResultBase resultBase, Func predicate, out IEnumerable result) where TError : IError + { + if(predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return ResultHelper.HasError(resultBase.Errors, predicate, out result); + } + + /// + public static bool HasError(this IResultBase resultBase, Func predicate) + { + return HasError(resultBase, predicate, out _); + } + + /// + public static bool HasError(this IResultBase resultBase, Func predicate, out IEnumerable result) + { + if(predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return ResultHelper.HasError(resultBase.Errors, predicate, out result); + } + + /// + public static bool HasException(this IResultBase resultBase) where TException : Exception + { + return HasException(resultBase, out _); + } + + /// + public static bool HasException(this IResultBase resultBase, out IEnumerable result) where TException : Exception + { + return HasException(resultBase, error => true, out result); + } + + /// + public static bool HasException(this IResultBase resultBase, Func predicate) where TException : Exception + { + return HasException(resultBase, predicate, out _); + } + + /// + public static bool HasException(this IResultBase resultBase, Func predicate, out IEnumerable result) where TException : Exception + { + if(predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return ResultHelper.HasException(resultBase.Errors, predicate, out result); + } + + /// + public static bool HasSuccess(this IResultBase resultBase) where TSuccess : ISuccess + { + return HasSuccess(resultBase, success => true, out _); + } + + /// + public static bool HasSuccess(this IResultBase resultBase, out IEnumerable result) where TSuccess : ISuccess + { + return HasSuccess(resultBase, success => true, out result); + } + + /// + public static bool HasSuccess(this IResultBase resultBase, Func predicate) where TSuccess : ISuccess + { + return HasSuccess(resultBase, predicate, out _); + } + + /// + public static bool HasSuccess(this IResultBase resultBase, Func predicate, out IEnumerable result) where TSuccess : ISuccess + { + return ResultHelper.HasSuccess(resultBase.Successes, predicate, out result); + } + + /// + public static bool HasSuccess(this IResultBase resultBase, Func predicate, out IEnumerable result) + { + return ResultHelper.HasSuccess(resultBase.Successes, predicate, out result); + } + + /// + public static bool HasSuccess(this IResultBase resultBase, Func predicate) + { + return ResultHelper.HasSuccess(resultBase.Successes, predicate, out _); + } + + /// + public static void Deconstruct(this IResultBase resultBase, out bool isSuccess, out bool isFailed) + { + isSuccess = resultBase.IsSuccess; + isFailed = resultBase.IsFailed; + } + + /// + public static void Deconstruct(this IResultBase resultBase, out bool isSuccess, out bool isFailed, out IReadOnlyList errors) + { + isSuccess = resultBase.IsSuccess; + isFailed = resultBase.IsFailed; + errors = isFailed ? resultBase.Errors : (IReadOnlyList)Array.Empty(); + } + + #endregion + + + #region Methods from ResultBase (return the same type, for fluent syntax) + + /// + public static TResult WithReason(this TResult result, IReason reason) + where TResult : IResultBase + { + result.Reasons.Add(reason); + return result; + } + + /// + public static TResult WithReasons(this TResult result, IEnumerable reasons) + where TResult : IResultBase + { + result.Reasons.AddRange(reasons); + return result; + } + + /// + public static TResult WithError(this TResult result, string errorMessage) + where TResult : IResultBase + { + return result.WithError(Result.Settings.ErrorFactory(errorMessage)); + } + + /// + public static TResult WithError(this TResult result, IError error) + where TResult : IResultBase + { + return result.WithReason(error); + } + + /// + public static TResult WithErrors(this TResult result, IEnumerable errors) + where TResult : IResultBase + { + return result.WithReasons(errors); + } + + /// + public static TResult WithErrors(this TResult result, IEnumerable errors) + where TResult : IResultBase + { + return result.WithReasons(errors.Select(errorMessage => Result.Settings.ErrorFactory(errorMessage))); + } + + /// + public static TResult WithSuccess(this TResult result, string successMessage) + where TResult : IResultBase + { + return result.WithSuccess(Result.Settings.SuccessFactory(successMessage)); + } + + /// + public static TResult WithSuccess(this TResult result, ISuccess success) + where TResult : IResultBase + { + return result.WithReason(success); + } + + /// + public static TResult WithSuccesses(this TResult result, IEnumerable successes) + where TResult : IResultBase + { + foreach(var success in successes) + { + result.WithSuccess(success); + } + + return result; + } + + + #endregion + + } + +} \ No newline at end of file