diff --git a/src/FluentResults.Test/ResultTValueExtensionsTests.cs b/src/FluentResults.Test/ResultTValueExtensionsTests.cs new file mode 100644 index 0000000..a45fe6d --- /dev/null +++ b/src/FluentResults.Test/ResultTValueExtensionsTests.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 ResultTValueExtensionsTests + { + 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); + } + } +} 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 diff --git a/src/FluentResults/Extensions/ResultExtensions.cs b/src/FluentResults/Extensions/ResultExtensions.cs index f11dfbe..138a2bc 100644 --- a/src/FluentResults/Extensions/ResultExtensions.cs +++ b/src/FluentResults/Extensions/ResultExtensions.cs @@ -1,10 +1,13 @@ using System; +using System.Linq; using System.Threading.Tasks; 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; @@ -148,5 +151,304 @@ public static async Task> ToResult(this ValueTask var result = await resultTask; return result.ToResult(value); } + + #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; + return result.MapErrors(errorMapper); + } + + public static async ValueTask MapErrors(this ValueTask resultTask, Func errorMapper) + { + var result = await resultTask; + return result.MapErrors(errorMapper); + } +#endif + + 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); + } + +#if false + 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); + } +#endif + + 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); + } + +#if false + 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); + } +#endif + + 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); + } + + #endregion + + #region Convert (Value)Task of IResultBase into Task of Result + + public static async Task ToResult(this Task resultTask) + { + var result = await resultTask; + return new Result() + .WithReasons(result.Reasons); + } + + public static async ValueTask ToResult(this ValueTask resultTask) + { + var result = await resultTask; + 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 + ? 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; + } +#endif + #endregion + + } } diff --git a/src/FluentResults/Extensions/ResultTValueExtensions.cs b/src/FluentResults/Extensions/ResultTValueExtensions.cs new file mode 100644 index 0000000..d394abb --- /dev/null +++ b/src/FluentResults/Extensions/ResultTValueExtensions.cs @@ -0,0 +1,147 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; + +// ReSharper disable once CheckNamespace +namespace FluentResults +{ + /// + /// Extensions methods for + /// + /// + /// 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 + /// + /// + /// 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