diff --git a/CSharpEssentials.Results/Extensions/ResultExtensions.cs b/CSharpEssentials.Results/Extensions/ResultExtensions.cs index e60e8af..3164971 100644 --- a/CSharpEssentials.Results/Extensions/ResultExtensions.cs +++ b/CSharpEssentials.Results/Extensions/ResultExtensions.cs @@ -17,6 +17,13 @@ public static partial class ResultExtensions /// public static Result ToResult(this IEnumerable errors) => errors.ToArray(); + /// + /// Converts an array of errors to a result directly without copying. + /// + /// + /// + public static Result ToResult(this Error[] errors) => Result.Failure(errors); + /// /// Converts a value to a result. /// @@ -40,4 +47,12 @@ public static partial class ResultExtensions /// /// public static Result ToResult(this IEnumerable errors) => errors.ToArray(); + + /// + /// Converts an array of errors to a result directly without copying. + /// + /// + /// + /// + public static Result ToResult(this Error[] errors) => Result.Failure(errors); } diff --git a/CSharpEssentials.Results/Modules/Result.Bind.cs b/CSharpEssentials.Results/Modules/Result.Bind.cs index 15ee260..d60856e 100644 --- a/CSharpEssentials.Results/Modules/Result.Bind.cs +++ b/CSharpEssentials.Results/Modules/Result.Bind.cs @@ -13,7 +13,7 @@ public readonly partial record struct Result public Result Bind(Func> func) { if (IsFailure) - return Errors; + return Result.Failure(_errors); return func(); } @@ -25,7 +25,7 @@ public Result Bind(Func> func) public Result Bind(Func func) { if (IsFailure) - return Errors; + return this; return func(); } @@ -38,7 +38,7 @@ public Result Bind(Func func) public Task> Bind(Func>> func) { if (IsFailure) - return Errors.ToResult().AsTask(); + return Result.Failure(_errors).AsTask(); return func(); } @@ -50,7 +50,7 @@ public Task> Bind(Func>> func) public Task Bind(Func> func) { if (IsFailure) - return ((Result)Errors).AsTask(); + return this.AsTask(); return func(); } @@ -63,7 +63,7 @@ public Task Bind(Func> func) public ValueTask> Bind(Func>> valueTask) { if (IsFailure) - return Failure(Errors).AsValueTask(); + return Result.Failure(_errors).AsValueTask(); return valueTask(); } @@ -75,7 +75,7 @@ public ValueTask> Bind(Func>> valueTas public ValueTask Bind(Func> valueTask) { if (IsFailure) - return Failure(Errors).AsValueTask(); + return this.AsValueTask(); return valueTask(); } } diff --git a/CSharpEssentials.Results/Modules/Result.Map.cs b/CSharpEssentials.Results/Modules/Result.Map.cs index dace847..494b50e 100644 --- a/CSharpEssentials.Results/Modules/Result.Map.cs +++ b/CSharpEssentials.Results/Modules/Result.Map.cs @@ -12,7 +12,7 @@ public readonly partial record struct Result public Result Map(Func map) { if (IsFailure) - return Errors; + return Result.Failure(_errors); return map(); } @@ -25,7 +25,7 @@ public Result Map(Func map) public Result Map(Func> map) { if (IsFailure) - return Errors; + return Result.Failure(_errors); return map(); } } @@ -58,7 +58,7 @@ public static async Task> MapAsync(this Task task, Fu { Result result = await task.WithCancellation(cancellationToken); if (result.IsFailure) - return result.Errors; + return Result.Failure(result.ErrorsOrEmptyArray); return await map().WithCancellation(cancellationToken); } diff --git a/CSharpEssentials.Results/Modules/Result.MapError.cs b/CSharpEssentials.Results/Modules/Result.MapError.cs index df4981e..5334b12 100644 --- a/CSharpEssentials.Results/Modules/Result.MapError.cs +++ b/CSharpEssentials.Results/Modules/Result.MapError.cs @@ -8,13 +8,13 @@ public Result MapError(Func errorMapper) { if (IsSuccess) return this; - return errorMapper(Errors); + return Result.Failure(errorMapper(Errors)); } public Result MapError(Func errorMapper) { if (IsSuccess) return this; - return errorMapper(FirstError); + return Result.Failure(errorMapper(FirstError)); } } diff --git a/CSharpEssentials.Results/Modules/ResultT.Bind.cs b/CSharpEssentials.Results/Modules/ResultT.Bind.cs index 498c605..da4854f 100644 --- a/CSharpEssentials.Results/Modules/ResultT.Bind.cs +++ b/CSharpEssentials.Results/Modules/ResultT.Bind.cs @@ -13,7 +13,7 @@ public readonly partial record struct Result public Result Bind(Func> func) { if (IsFailure) - return Errors; + return Result.Failure(_errors); return func(Value); } @@ -25,7 +25,7 @@ public Result Bind(Func> func) public Result Bind(Func func) { if (IsFailure) - return Errors; + return Result.Failure(_errors); return func(Value); } @@ -38,7 +38,7 @@ public Result Bind(Func func) public Task> Bind(Func>> func) { if (IsFailure) - return Errors.ToResult().AsTask(); + return Result.Failure(_errors).AsTask(); return func(Value); } @@ -50,7 +50,7 @@ public Task> Bind(Func>> func) public Task Bind(Func> func) { if (IsFailure) - return ((Result)Errors).AsTask(); + return Result.Failure(_errors).AsTask(); return func(Value); } @@ -63,7 +63,7 @@ public Task Bind(Func> func) public ValueTask> Bind(Func>> valueTask) { if (IsFailure) - return Errors.ToResult().AsValueTask(); + return Result.Failure(_errors).AsValueTask(); return valueTask(Value); } @@ -76,7 +76,7 @@ public ValueTask> Bind(Func>> public ValueTask Bind(Func> valueTask) { if (IsFailure) - return ((Result)Errors).AsValueTask(); + return Result.Failure(_errors).AsValueTask(); return valueTask(Value); } } diff --git a/CSharpEssentials.Results/Modules/ResultT.Else.cs b/CSharpEssentials.Results/Modules/ResultT.Else.cs index a20ad26..0cbcb7b 100644 --- a/CSharpEssentials.Results/Modules/ResultT.Else.cs +++ b/CSharpEssentials.Results/Modules/ResultT.Else.cs @@ -14,7 +14,7 @@ public Result Else(Func onFailure) { if (IsSuccess) return Value; - return onFailure(Errors).ToResult(); + return Result.Failure(onFailure(Errors)); } /// @@ -26,7 +26,7 @@ public Result Else(Func onFailure) { if (IsSuccess) return Value; - return onFailure(Errors).ToResult(); + return Result.Failure(onFailure(Errors)); } /// @@ -38,7 +38,7 @@ public Result Else(Error error) { if (IsSuccess) return Value; - return error.ToResult(); + return Result.Failure(error); } /// @@ -90,7 +90,7 @@ public async Task> ElseAsync(Func> onFailure if (IsSuccess) return Value; Error result = await onFailure(Errors).WithCancellation(cancellationToken); - return result.ToResult(); + return Result.Failure(result); } /// @@ -104,7 +104,7 @@ public async Task> ElseAsync(Func> onFailu if (IsSuccess) return Value; Error[] result = await onFailure(Errors).WithCancellation(cancellationToken); - return result.ToResult(); + return Result.Failure(result); } /// @@ -118,7 +118,7 @@ public async Task> ElseAsync(Task error, CancellationToken if (IsSuccess) return Value; Error result = await error.WithCancellation(cancellationToken); - return result.ToResult(); + return Result.Failure(result); } /// diff --git a/CSharpEssentials.Results/Modules/ResultT.Ensure.cs b/CSharpEssentials.Results/Modules/ResultT.Ensure.cs index c873a99..72671c1 100644 --- a/CSharpEssentials.Results/Modules/ResultT.Ensure.cs +++ b/CSharpEssentials.Results/Modules/ResultT.Ensure.cs @@ -9,42 +9,42 @@ public Result Ensure(Func predicate, Error error) { if (IsFailure) return this; - return predicate(Value) ? this : error.ToResult(); + return predicate(Value) ? this : Result.Failure(error); } public Result Ensure(Func predicate, Func errorFactory) { if (IsFailure) return this; - return predicate(Value) ? this : errorFactory(Value).ToResult(); + return predicate(Value) ? this : Result.Failure(errorFactory(Value)); } public Result EnsureNotNull(Error error) { if (IsFailure) return this; - return Value is not null ? this : error.ToResult(); + return Value is not null ? this : Result.Failure(error); } public Result EnsureNotNull(Func errorFactory) { if (IsFailure) return this; - return Value is not null ? this : errorFactory(Value).ToResult(); + return Value is not null ? this : Result.Failure(errorFactory(Value)); } public async Task> EnsureAsync(Func> predicate, Error error, CancellationToken cancellationToken = default) { if (IsFailure) return this; - return await predicate(Value).WithCancellation(cancellationToken) ? this : error.ToResult(); + return await predicate(Value).WithCancellation(cancellationToken) ? this : Result.Failure(error); } public async Task> EnsureAsync(Func> predicate, Func errorFactory, CancellationToken cancellationToken = default) { if (IsFailure) return this; - return await predicate(Value).WithCancellation(cancellationToken) ? this : errorFactory(Value).ToResult(); + return await predicate(Value).WithCancellation(cancellationToken) ? this : Result.Failure(errorFactory(Value)); } } diff --git a/CSharpEssentials.Results/Modules/ResultT.Map.cs b/CSharpEssentials.Results/Modules/ResultT.Map.cs index cdf59fc..122f577 100644 --- a/CSharpEssentials.Results/Modules/ResultT.Map.cs +++ b/CSharpEssentials.Results/Modules/ResultT.Map.cs @@ -12,7 +12,7 @@ public readonly partial record struct Result public Result Map(Func map) { if (IsFailure) - return Errors; + return Result.Failure(_errors); return map(Value); } @@ -25,7 +25,7 @@ public Result Map(Func map) public Result Map(Func> map) { if (IsFailure) - return Errors; + return Result.Failure(_errors); return map(Value); } } @@ -60,7 +60,7 @@ public static async Task> MapAsync(this Task result = await task.WithCancellation(cancellationToken); if (result.IsFailure) - return result.Errors; + return Result.Failure(result.ErrorsOrEmptyArray); return await map(result.Value).WithCancellation(cancellationToken); } diff --git a/CSharpEssentials.Results/Modules/ResultT.MapError.cs b/CSharpEssentials.Results/Modules/ResultT.MapError.cs index eab2730..a523389 100644 --- a/CSharpEssentials.Results/Modules/ResultT.MapError.cs +++ b/CSharpEssentials.Results/Modules/ResultT.MapError.cs @@ -8,13 +8,13 @@ public Result MapError(Func errorMapper) { if (IsSuccess) return this; - return errorMapper(Errors).ToResult(); + return Result.Failure(errorMapper(Errors)); } public Result MapError(Func errorMapper) { if (IsSuccess) return this; - return errorMapper(FirstError).ToResult(); + return Result.Failure(errorMapper(FirstError)); } } diff --git a/CSharpEssentials.Results/Modules/ResultT.Select.cs b/CSharpEssentials.Results/Modules/ResultT.Select.cs index 43155e3..9d50115 100644 --- a/CSharpEssentials.Results/Modules/ResultT.Select.cs +++ b/CSharpEssentials.Results/Modules/ResultT.Select.cs @@ -96,7 +96,7 @@ public static async Task> SelectAsync(this Task result = await task.WithCancellation(cancellationToken); if (result.IsFailure) - return result.Errors; + return Result.Failure(result.ErrorsOrEmptyArray); return await selector(result.Value).WithCancellation(cancellationToken); } diff --git a/CSharpEssentials.Results/Modules/ResultT.Then.cs b/CSharpEssentials.Results/Modules/ResultT.Then.cs index bf07e85..f0c0fe2 100644 --- a/CSharpEssentials.Results/Modules/ResultT.Then.cs +++ b/CSharpEssentials.Results/Modules/ResultT.Then.cs @@ -13,7 +13,7 @@ public readonly partial record struct Result public Result Then(Func> onSuccess) { if (IsFailure) - return Errors.ToResult(); + return Result.Failure(_errors); return onSuccess(Value); } @@ -25,7 +25,7 @@ public Result Then(Func> onSuccess) public Result ThenDo(Action action) { if (IsFailure) - return Errors; + return this; action(Value); return this; } @@ -39,7 +39,7 @@ public Result ThenDo(Action action) public Result Then(Func onSuccess) { if (IsFailure) - return Errors.ToResult(); + return Result.Failure(_errors); return onSuccess(Value).ToResult(); } @@ -53,7 +53,7 @@ public Result Then(Func onSuccess) public async Task> ThenAsync(Func>> onSuccess, CancellationToken cancellationToken = default) { if (IsFailure) - return Errors.ToResult(); + return Result.Failure(_errors); return await onSuccess(Value).WithCancellation(cancellationToken); } @@ -66,7 +66,7 @@ public async Task> ThenAsync(Func>> onSucces public async Task> ThenDoAsync(Func action, CancellationToken cancellationToken = default) { if (IsFailure) - return Errors; + return this; await action(Value).WithCancellation(cancellationToken); return this; } @@ -81,7 +81,7 @@ public async Task> ThenDoAsync(Func action, Cancell public async Task> ThenAsync(Func> onSuccess, CancellationToken cancellationToken = default) { if (IsFailure) - return Errors.ToResult(); + return Result.Failure(_errors); T? result = await onSuccess(Value).WithCancellation(cancellationToken); return result.ToResult(); } diff --git a/CSharpEssentials.Results/Modules/ResultT.ThenEnsure.cs b/CSharpEssentials.Results/Modules/ResultT.ThenEnsure.cs index 9e2104c..86d3094 100644 --- a/CSharpEssentials.Results/Modules/ResultT.ThenEnsure.cs +++ b/CSharpEssentials.Results/Modules/ResultT.ThenEnsure.cs @@ -14,7 +14,7 @@ public Result ThenEnsure(Func validator) if (IsFailure) return this; Result result = validator(Value); - return result.IsSuccess ? this : result.Errors.ToResult(); + return result.IsSuccess ? this : Result.Failure(result.ErrorsOrEmptyArray); } public async Task> ThenEnsureAsync(Func>> validator, CancellationToken cancellationToken = default) @@ -31,7 +31,7 @@ public async Task> ThenEnsureAsync(Func> val return this; cancellationToken.ThrowIfCancellationRequested(); Result result = await validator(Value); - return result.IsSuccess ? this : result.Errors.ToResult(); + return result.IsSuccess ? this : Result.Failure(result.ErrorsOrEmptyArray); } } diff --git a/CSharpEssentials.Results/ResultFactory.cs b/CSharpEssentials.Results/ResultFactory.cs index 8951af5..456d36b 100644 --- a/CSharpEssentials.Results/ResultFactory.cs +++ b/CSharpEssentials.Results/ResultFactory.cs @@ -25,6 +25,13 @@ public readonly partial record struct Result /// public static Result Failure(params IEnumerable errors) => From(errors); + /// + /// Creates a new failure result directly using the specified errors array without copying. + /// + /// + /// + public static Result Failure(Error[] errors) => new(true, ResultLogic.ValidateErrorsArray(errors)); + /// /// Converts a collection of errors to a result. /// @@ -49,7 +56,7 @@ public static Result And(params IEnumerable results) foreach (IResultBase result in results) { if (result.IsFailure) - return result.Errors; + return Failure(result.ErrorsOrEmptyArray); } return Success(); } @@ -102,6 +109,13 @@ public static Result Or(params IEnumerable results) /// public static Result Failure(params IEnumerable errors) => Result.From(errors); /// + /// Creates a new failure result directly using the specified errors array without copying. + /// + /// + /// + /// + public static Result Failure(Error[] errors) => Result.Failure(errors); + /// /// Converts an error to a result. /// /// @@ -122,7 +136,7 @@ public static Result Or(params IEnumerable results) #else public static implicit operator Result(Error error) => new([error]); #endif - public static implicit operator Result(Error[] errors) => new(errors); + public static implicit operator Result(Error[] errors) => Failure(errors); public static implicit operator Result(List errors) => new(errors); public static implicit operator Result(HashSet errors) => new(errors); public static implicit operator Result(bool isSuccess) => isSuccess ? Success() : Error.False; diff --git a/CSharpEssentials.Results/ResultLogic.cs b/CSharpEssentials.Results/ResultLogic.cs index f4fe6f9..0d273fe 100644 --- a/CSharpEssentials.Results/ResultLogic.cs +++ b/CSharpEssentials.Results/ResultLogic.cs @@ -4,6 +4,21 @@ namespace CSharpEssentials.ResultPattern; internal static class ResultLogic { + internal static Error[] ValidateErrorsArray(Error[] errors) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(errors); +#else + if (errors is null) + throw new ArgumentNullException(nameof(errors)); +#endif + + if (errors.Length == 0) + throw CreateEmptyErrorArrayException(); + + return errors; + } + internal static bool CheckIfErrorsAreEqual(Error[] left, Error[] right) { if (ReferenceEquals(left, right)) diff --git a/CSharpEssentials.Results/ResultTFactory.cs b/CSharpEssentials.Results/ResultTFactory.cs index c99dd29..0c73a65 100644 --- a/CSharpEssentials.Results/ResultTFactory.cs +++ b/CSharpEssentials.Results/ResultTFactory.cs @@ -26,6 +26,13 @@ public readonly partial record struct Result /// public static Result Failure(params IEnumerable errors) => From(errors); + /// + /// Creates a new failure result directly using the specified errors array without copying. + /// + /// + /// + public static Result Failure(Error[] errors) => new(false, default, ResultLogic.ValidateErrorsArray(errors)); + /// /// Converts a collection of errors to a result. /// @@ -96,7 +103,7 @@ public static Result Or(params IEnumerable> results) #else public static implicit operator Result(Error error) => new([error]); #endif - public static implicit operator Result(Error[] errors) => new(errors); + public static implicit operator Result(Error[] errors) => Failure(errors); public static implicit operator Result(List errors) => new(errors); public static implicit operator Result(HashSet errors) => new(errors); diff --git a/CSharpEssentials.Tests/Results/ResultFactoryTests.cs b/CSharpEssentials.Tests/Results/ResultFactoryTests.cs index 938a485..33e3786 100644 --- a/CSharpEssentials.Tests/Results/ResultFactoryTests.cs +++ b/CSharpEssentials.Tests/Results/ResultFactoryTests.cs @@ -33,6 +33,22 @@ public void Failure_WithMultipleErrors_ShouldCreateFailureResult() result.Errors.Should().HaveCount(2); } + [Fact] + public void Failure_WithEmptyErrorsArray_ShouldThrow() + { + Action act = () => Result.Failure([]); + + act.Should().Throw(); + } + + [Fact] + public void Failure_WithNullErrorsArray_ShouldThrow() + { + Action act = () => Result.Failure((Error[]?)null!); + + act.Should().Throw(); + } + [Fact] public void From_WithErrors_ShouldCreateFailureResult() { @@ -71,6 +87,22 @@ public void FailureT_WithMultipleErrors_ShouldCreateFailureResult() result.Errors.Should().HaveCount(2); } + [Fact] + public void FailureT_WithEmptyErrorsArray_ShouldThrow() + { + Action act = () => Result.Failure([]); + + act.Should().Throw(); + } + + [Fact] + public void FailureT_WithNullErrorsArray_ShouldThrow() + { + Action act = () => Result.Failure((Error[]?)null!); + + act.Should().Throw(); + } + [Fact] public void FromT_WithError_ShouldCreateFailureResult() { @@ -90,4 +122,3 @@ public void FromT_WithErrors_ShouldCreateFailureResult() result.Errors.Should().Contain(TestData.Errors.Failure); } } - diff --git a/CSharpEssentials.Tests/Results/ResultZeroAllocationTests.cs b/CSharpEssentials.Tests/Results/ResultZeroAllocationTests.cs new file mode 100644 index 0000000..6b9b517 --- /dev/null +++ b/CSharpEssentials.Tests/Results/ResultZeroAllocationTests.cs @@ -0,0 +1,201 @@ +using Xunit; +using CSharpEssentials.Errors; +using CSharpEssentials.ResultPattern; + +namespace CSharpEssentials.Tests.Results; + +public class ResultZeroAllocationTests +{ + [Fact] + public void ResultFailure_WithErrorsArray_ShouldNotCopyArray() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + + // Act + Result result = Result.Failure(errors); + + // Assert + Assert.True(result.IsFailure); + Assert.Same(errors, result.ErrorsOrEmptyArray); + } + + [Fact] + public void ResultGenericFailure_WithErrorsArray_ShouldNotCopyArray() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + + // Act + Result result = Result.Failure(errors); + + // Assert + Assert.True(result.IsFailure); + Assert.Same(errors, result.ErrorsOrEmptyArray); + } + + [Fact] + public void ResultExtensionsToResult_WithErrorsArray_ShouldNotCopyArray() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + + // Act + Result result = errors.ToResult(); + Result resultGeneric = errors.ToResult(); + + // Assert + Assert.True(result.IsFailure); + Assert.Same(errors, result.ErrorsOrEmptyArray); + Assert.True(resultGeneric.IsFailure); + Assert.Same(errors, resultGeneric.ErrorsOrEmptyArray); + } + + [Fact] + public void ResultImplicitConversions_WithErrorsArray_ShouldNotCopyArray() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + + // Act + Result result = errors; + Result resultGeneric = errors; + + // Assert + Assert.True(result.IsFailure); + Assert.Same(errors, result.ErrorsOrEmptyArray); + Assert.True(resultGeneric.IsFailure); + Assert.Same(errors, resultGeneric.ErrorsOrEmptyArray); + } + + [Fact] + public void ResultBind_OnFailure_ShouldReturnOriginalErrorsArrayReference() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + Result result = Result.Failure(errors); + + // Act + Result boundGeneric = result.Bind(() => Result.Success("test")); + Result boundNonGeneric = result.Bind(() => Result.Success()); + + // Assert + Assert.True(boundGeneric.IsFailure); + Assert.Same(errors, boundGeneric.ErrorsOrEmptyArray); + + Assert.True(boundNonGeneric.IsFailure); + Assert.Same(errors, boundNonGeneric.ErrorsOrEmptyArray); + } + + [Fact] + public void ResultTBind_OnFailure_ShouldReturnOriginalErrorsArrayReference() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + Result result = Result.Failure(errors); + + // Act + Result boundGeneric = result.Bind(val => Result.Success("test")); + Result boundNonGeneric = result.Bind(val => Result.Success()); + + // Assert + Assert.True(boundGeneric.IsFailure); + Assert.Same(errors, boundGeneric.ErrorsOrEmptyArray); + + Assert.True(boundNonGeneric.IsFailure); + Assert.Same(errors, boundNonGeneric.ErrorsOrEmptyArray); + } + + [Fact] + public void ResultMap_OnFailure_ShouldReturnOriginalErrorsArrayReference() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + Result result = Result.Failure(errors); + + // Act + Result mapped = result.Map(() => "test"); + Result mappedResult = result.Map(() => Result.Success("test")); + + // Assert + Assert.True(mapped.IsFailure); + Assert.Same(errors, mapped.ErrorsOrEmptyArray); + + Assert.True(mappedResult.IsFailure); + Assert.Same(errors, mappedResult.ErrorsOrEmptyArray); + } + + [Fact] + public void ResultTThen_OnFailure_ShouldReturnOriginalErrorsArrayReference() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + Result result = Result.Failure(errors); + + // Act + Result thenResult = result.Then(val => Result.Success("test")); + Result thenDoResult = result.ThenDo(val => { }); + Result thenValueResult = result.Then(val => "test"); + + // Assert + Assert.True(thenResult.IsFailure); + Assert.Same(errors, thenResult.ErrorsOrEmptyArray); + + Assert.True(thenDoResult.IsFailure); + Assert.Same(errors, thenDoResult.ErrorsOrEmptyArray); + + Assert.True(thenValueResult.IsFailure); + Assert.Same(errors, thenValueResult.ErrorsOrEmptyArray); + } + + [Fact] + public void ResultTThenEnsure_OnFailure_ShouldReturnOriginalErrorsArrayReference() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + Result result = Result.Failure(errors); + + // Act + Result ensureResult = result.ThenEnsure(val => Result.Success(val)); + Result ensureNonGenericResult = result.ThenEnsure(val => Result.Success()); + + // Assert + Assert.True(ensureResult.IsFailure); + Assert.Same(errors, ensureResult.ErrorsOrEmptyArray); + + Assert.True(ensureNonGenericResult.IsFailure); + Assert.Same(errors, ensureNonGenericResult.ErrorsOrEmptyArray); + } + + [Fact] + public void ResultTMapError_OnFailure_ShouldPropagateMappedErrorsZeroAllocation() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + Result result = Result.Failure(errors); + Error[] mappedErrors = [Error.Conflict("Conflict", "Message")]; + + // Act + Result mappedResult = result.MapError(errs => mappedErrors); + + // Assert + Assert.True(mappedResult.IsFailure); + Assert.Same(mappedErrors, mappedResult.ErrorsOrEmptyArray); + } + + [Fact] + public void ResultTElse_OnFailure_ShouldPropagateMappedErrorsZeroAllocation() + { + // Arrange + Error[] errors = [Error.Validation("Code", "Message")]; + Result result = Result.Failure(errors); + Error[] newErrors = [Error.Conflict("Conflict", "Message")]; + + // Act + Result elseResult = result.Else(errs => newErrors); + + // Assert + Assert.True(elseResult.IsFailure); + Assert.Same(newErrors, elseResult.ErrorsOrEmptyArray); + } +} diff --git a/README.MD b/README.MD index 9b1d728..c8cea3a 100644 --- a/README.MD +++ b/README.MD @@ -5,7 +5,7 @@ # CSharpEssentials [![Build](https://github.com/senrecep/CSharpEssentials/actions/workflows/build.yml/badge.svg)](https://github.com/senrecep/CSharpEssentials/actions/workflows/build.yml) -[![Tests](https://img.shields.io/badge/tests-2734%20passing-brightgreen)](https://github.com/senrecep/CSharpEssentials/actions/workflows/build.yml) +[![Tests](https://img.shields.io/badge/tests-2749%20passing-brightgreen)](https://github.com/senrecep/CSharpEssentials/actions/workflows/build.yml) [![NuGet](https://img.shields.io/nuget/v/CSharpEssentials.svg)](https://www.nuget.org/packages/CSharpEssentials) [![Downloads](https://img.shields.io/nuget/dt/CSharpEssentials.svg)](https://www.nuget.org/packages/CSharpEssentials) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/senrecep/CSharpEssentials/blob/main/LICENCE)