diff --git a/CSharpEssentials.Any/AnyCollectionExtensions.cs b/CSharpEssentials.Any/AnyCollectionExtensions.cs new file mode 100644 index 0000000..e282c27 --- /dev/null +++ b/CSharpEssentials.Any/AnyCollectionExtensions.cs @@ -0,0 +1,154 @@ +namespace CSharpEssentials.Any; + +public static partial class AnyExtensions +{ + private static IEnumerable EnsureSource(IEnumerable source) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(source); +#else + if (source is null) + throw new ArgumentNullException(nameof(source)); +#endif + + return source; + } + + private static Func EnsureSelector(Func selector) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(selector); +#else + if (selector is null) + throw new ArgumentNullException(nameof(selector)); +#endif + + return selector; + } + + public static (T0[] First, T1[] Second) Traverse(this IEnumerable source, Func> selector) => + EnsureSource(source).Select(EnsureSelector(selector)).Partition(); + + public static (T0[] First, T1[] Second) Partition(this IEnumerable> source) + { + source = EnsureSource(source); + List first = []; + List second = []; + + foreach (Any item in source) + item.Switch(first: first.Add, second: second.Add); + + return (first.ToArray(), second.ToArray()); + } + + public static (T0[] First, T1[] Second, T2[] Third) Traverse(this IEnumerable source, Func> selector) => + EnsureSource(source).Select(EnsureSelector(selector)).Partition(); + + public static (T0[] First, T1[] Second, T2[] Third) Partition(this IEnumerable> source) + { + source = EnsureSource(source); + List first = []; + List second = []; + List third = []; + + foreach (Any item in source) + item.Switch(first: first.Add, second: second.Add, third: third.Add); + + return (first.ToArray(), second.ToArray(), third.ToArray()); + } + + public static (T0[] First, T1[] Second, T2[] Third, T3[] Fourth) Traverse(this IEnumerable source, Func> selector) => + EnsureSource(source).Select(EnsureSelector(selector)).Partition(); + + public static (T0[] First, T1[] Second, T2[] Third, T3[] Fourth) Partition(this IEnumerable> source) + { + source = EnsureSource(source); + List first = []; + List second = []; + List third = []; + List fourth = []; + + foreach (Any item in source) + item.Switch(first: first.Add, second: second.Add, third: third.Add, fourth: fourth.Add); + + return (first.ToArray(), second.ToArray(), third.ToArray(), fourth.ToArray()); + } + + public static (T0[] First, T1[] Second, T2[] Third, T3[] Fourth, T4[] Fifth) Traverse(this IEnumerable source, Func> selector) => + EnsureSource(source).Select(EnsureSelector(selector)).Partition(); + + public static (T0[] First, T1[] Second, T2[] Third, T3[] Fourth, T4[] Fifth) Partition(this IEnumerable> source) + { + source = EnsureSource(source); + List first = []; + List second = []; + List third = []; + List fourth = []; + List fifth = []; + + foreach (Any item in source) + item.Switch(first: first.Add, second: second.Add, third: third.Add, fourth: fourth.Add, fifth: fifth.Add); + + return (first.ToArray(), second.ToArray(), third.ToArray(), fourth.ToArray(), fifth.ToArray()); + } + + public static (T0[] First, T1[] Second, T2[] Third, T3[] Fourth, T4[] Fifth, T5[] Sixth) Traverse(this IEnumerable source, Func> selector) => + EnsureSource(source).Select(EnsureSelector(selector)).Partition(); + + public static (T0[] First, T1[] Second, T2[] Third, T3[] Fourth, T4[] Fifth, T5[] Sixth) Partition(this IEnumerable> source) + { + source = EnsureSource(source); + List first = []; + List second = []; + List third = []; + List fourth = []; + List fifth = []; + List sixth = []; + + foreach (Any item in source) + item.Switch(first: first.Add, second: second.Add, third: third.Add, fourth: fourth.Add, fifth: fifth.Add, sixth: sixth.Add); + + return (first.ToArray(), second.ToArray(), third.ToArray(), fourth.ToArray(), fifth.ToArray(), sixth.ToArray()); + } + + public static (T0[] First, T1[] Second, T2[] Third, T3[] Fourth, T4[] Fifth, T5[] Sixth, T6[] Seventh) Traverse(this IEnumerable source, Func> selector) => + EnsureSource(source).Select(EnsureSelector(selector)).Partition(); + + public static (T0[] First, T1[] Second, T2[] Third, T3[] Fourth, T4[] Fifth, T5[] Sixth, T6[] Seventh) Partition(this IEnumerable> source) + { + source = EnsureSource(source); + List first = []; + List second = []; + List third = []; + List fourth = []; + List fifth = []; + List sixth = []; + List seventh = []; + + foreach (Any item in source) + item.Switch(first: first.Add, second: second.Add, third: third.Add, fourth: fourth.Add, fifth: fifth.Add, sixth: sixth.Add, seventh: seventh.Add); + + return (first.ToArray(), second.ToArray(), third.ToArray(), fourth.ToArray(), fifth.ToArray(), sixth.ToArray(), seventh.ToArray()); + } + + public static (T0[] First, T1[] Second, T2[] Third, T3[] Fourth, T4[] Fifth, T5[] Sixth, T6[] Seventh, T7[] Eighth) Traverse(this IEnumerable source, Func> selector) => + EnsureSource(source).Select(EnsureSelector(selector)).Partition(); + + public static (T0[] First, T1[] Second, T2[] Third, T3[] Fourth, T4[] Fifth, T5[] Sixth, T6[] Seventh, T7[] Eighth) Partition(this IEnumerable> source) + { + source = EnsureSource(source); + List first = []; + List second = []; + List third = []; + List fourth = []; + List fifth = []; + List sixth = []; + List seventh = []; + List eighth = []; + + foreach (Any item in source) + item.Switch(first: first.Add, second: second.Add, third: third.Add, fourth: fourth.Add, fifth: fifth.Add, sixth: sixth.Add, seventh: seventh.Add, eighth: eighth.Add); + + return (first.ToArray(), second.ToArray(), third.ToArray(), fourth.ToArray(), fifth.ToArray(), sixth.ToArray(), seventh.ToArray(), eighth.ToArray()); + } +} diff --git a/CSharpEssentials.Any/AnyExtensions.cs b/CSharpEssentials.Any/AnyExtensions.cs index aab67be..9fc1db7 100644 --- a/CSharpEssentials.Any/AnyExtensions.cs +++ b/CSharpEssentials.Any/AnyExtensions.cs @@ -1,6 +1,6 @@ namespace CSharpEssentials.Any; -public static class AnyExtensions +public static partial class AnyExtensions { #region Any diff --git a/CSharpEssentials.Maybe/Extensions/MaybeCollectionExtensions.cs b/CSharpEssentials.Maybe/Extensions/MaybeCollectionExtensions.cs new file mode 100644 index 0000000..0e0a416 --- /dev/null +++ b/CSharpEssentials.Maybe/Extensions/MaybeCollectionExtensions.cs @@ -0,0 +1,64 @@ +namespace CSharpEssentials.Maybe; + +public static partial class MaybeExtensions +{ + private static IEnumerable EnsureSource(IEnumerable source) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(source); +#else + if (source is null) + throw new ArgumentNullException(nameof(source)); +#endif + + return source; + } + + private static Func EnsureSelector(Func selector) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(selector); +#else + if (selector is null) + throw new ArgumentNullException(nameof(selector)); +#endif + + return selector; + } + + public static Maybe Sequence(this IEnumerable> source) + { + source = EnsureSource(source); + List values = []; + + foreach (Maybe maybe in source) + { + if (maybe.HasNoValue) + return Maybe.None; + + values.Add(maybe.Value); + } + + return values.ToArray(); + } + + public static Maybe Traverse(this IEnumerable source, Func> selector) => + EnsureSource(source).Select(EnsureSelector(selector)).Sequence(); + + public static (TValue[] Values, int NoneCount) Partition(this IEnumerable> source) + { + source = EnsureSource(source); + List values = []; + int noneCount = 0; + + foreach (Maybe maybe in source) + { + if (maybe.HasValue) + values.Add(maybe.Value); + else + noneCount++; + } + + return (values.ToArray(), noneCount); + } +} diff --git a/CSharpEssentials.Results/Extensions/ResultCollectionExtensions.cs b/CSharpEssentials.Results/Extensions/ResultCollectionExtensions.cs new file mode 100644 index 0000000..d067844 --- /dev/null +++ b/CSharpEssentials.Results/Extensions/ResultCollectionExtensions.cs @@ -0,0 +1,190 @@ +using CSharpEssentials.Errors; +using CSharpEssentials.ResultPattern.Interfaces; + +namespace CSharpEssentials.ResultPattern; + +public static partial class ResultExtensions +{ + private static IEnumerable EnsureSource(IEnumerable source) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(source); +#else + if (source is null) + throw new ArgumentNullException(nameof(source)); +#endif + + return source; + } + + private static Func EnsureSelector(Func selector) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(selector); +#else + if (selector is null) + throw new ArgumentNullException(nameof(selector)); +#endif + + return selector; + } + + public static Result CombineAll(this IEnumerable source) + { + source = EnsureSource(source); + List errors = []; + + foreach (Result result in source) + { + if (result.IsFailure) + errors.AddRange(result.ErrorsOrEmptyArray); + } + + return errors.Count == 0 ? Result.Success() : errors; + } + + public static Result CombineAll(this IEnumerable source) + { + source = EnsureSource(source); + List errors = []; + + foreach (IResultBase result in source) + { + if (result.IsFailure) + errors.AddRange(result.ErrorsOrEmptyArray); + } + + return errors.Count == 0 ? Result.Success() : errors; + } + + public static Result CombineAll(this IEnumerable> source) => source.Sequence(); + + public static Result CombineAll(this IEnumerable> source) => EnsureSource(source).Sequence(); + + public static Result Sequence(this IEnumerable> source) + { + source = EnsureSource(source); + List successes = []; + List errors = []; + + foreach (Result result in source) + { + if (result.IsSuccess) + successes.Add(result.Value); + else + errors.AddRange(result.ErrorsOrEmptyArray); + } + + return errors.Count == 0 ? successes.ToArray() : errors; + } + + public static Result Sequence(this IEnumerable> source) + { + source = EnsureSource(source); + List successes = []; + List errors = []; + + foreach (IResult result in source) + { + if (result.IsSuccess) + successes.Add(result.Value); + else + errors.AddRange(result.ErrorsOrEmptyArray); + } + + return errors.Count == 0 ? successes.ToArray() : errors; + } + + public static Result Traverse(this IEnumerable source, Func> selector) => + EnsureSource(source).Select(EnsureSelector(selector)).Sequence(); + + public static (TValue[] Successes, Error[] Errors) Partition(this IEnumerable> source) + { + source = EnsureSource(source); + List successes = []; + List errors = []; + + foreach (Result result in source) + { + if (result.IsSuccess) + successes.Add(result.Value); + else + errors.AddRange(result.ErrorsOrEmptyArray); + } + + return (successes.ToArray(), errors.ToArray()); + } + + public static (TValue[] Successes, Error[] Errors) Partition(this IEnumerable> source) + { + source = EnsureSource(source); + List successes = []; + List errors = []; + + foreach (IResult result in source) + { + if (result.IsSuccess) + successes.Add(result.Value); + else + errors.AddRange(result.ErrorsOrEmptyArray); + } + + return (successes.ToArray(), errors.ToArray()); + } + + public static Result FirstFailureOrSuccesses(this IEnumerable source) + { + source = EnsureSource(source); + foreach (Result result in source) + { + if (result.IsFailure) + return result.ErrorsOrEmptyArray; + } + + return Result.Success(); + } + + public static Result FirstFailureOrSuccesses(this IEnumerable source) + { + source = EnsureSource(source); + foreach (IResultBase result in source) + { + if (result.IsFailure) + return result.ErrorsOrEmptyArray; + } + + return Result.Success(); + } + + public static Result FirstFailureOrSuccesses(this IEnumerable> source) + { + source = EnsureSource(source); + List successes = []; + + foreach (Result result in source) + { + if (result.IsFailure) + return result.ErrorsOrEmptyArray; + + successes.Add(result.Value); + } + + return successes.ToArray(); + } + + public static Result FirstFailureOrSuccesses(this IEnumerable> source) + { + source = EnsureSource(source); + List successes = []; + + foreach (IResult result in source) + { + if (result.IsFailure) + return result.ErrorsOrEmptyArray; + + successes.Add(result.Value); + } + + return successes.ToArray(); + } +} diff --git a/CSharpEssentials.Tests/Any/AnyCollectionExtensionsTests.cs b/CSharpEssentials.Tests/Any/AnyCollectionExtensionsTests.cs new file mode 100644 index 0000000..058cef3 --- /dev/null +++ b/CSharpEssentials.Tests/Any/AnyCollectionExtensionsTests.cs @@ -0,0 +1,189 @@ +using CSharpEssentials.Any; +using FluentAssertions; +using System.Globalization; + +namespace CSharpEssentials.Tests.Any; + +public class AnyCollectionExtensionsTests +{ + [Fact] + public void Partition_T2_ShouldSplitBranches() + { + Any[] source = [1, "two", 3, "four"]; + + (int[] first, string[] second) = source.Partition(); + + first.Should().Equal(1, 3); + second.Should().Equal("two", "four"); + } + + [Fact] + public void Partition_T3_ShouldSplitAllBranches() + { + Any[] source = [1, "two", true, 3, false]; + + (int[] first, string[] second, bool[] third) = source.Partition(); + + first.Should().Equal(1, 3); + second.Should().Equal("two"); + third.Should().Equal(true, false); + } + + [Fact] + public void Partition_T4_ShouldSplitAllBranches() + { + Any[] source = [1, "two", true, 4.5m]; + + var result = source.Partition(); + + result.First.Should().Equal(1); + result.Second.Should().Equal("two"); + result.Third.Should().Equal(true); + result.Fourth.Should().Equal(4.5m); + } + + [Fact] + public void Partition_T5_ShouldSplitAllBranches() + { + Any[] source = + [ + 1, + "two", + true, + 4.5m, + Guid.Parse("11111111-1111-1111-1111-111111111111") + ]; + + var result = source.Partition(); + + result.First.Should().Equal(1); + result.Second.Should().Equal("two"); + result.Third.Should().Equal(true); + result.Fourth.Should().Equal(4.5m); + result.Fifth.Should().Equal(Guid.Parse("11111111-1111-1111-1111-111111111111")); + } + + [Fact] + public void Partition_T6_ShouldSplitAllBranches() + { + Any[] source = + [ + 1, + "two", + true, + 4.5m, + Guid.Parse("11111111-1111-1111-1111-111111111111"), + 6L + ]; + + var result = source.Partition(); + + result.First.Should().Equal(1); + result.Second.Should().Equal("two"); + result.Third.Should().Equal(true); + result.Fourth.Should().Equal(4.5m); + result.Fifth.Should().Equal(Guid.Parse("11111111-1111-1111-1111-111111111111")); + result.Sixth.Should().Equal(6L); + } + + [Fact] + public void Partition_T7_ShouldSplitAllBranches() + { + Any[] source = + [ + 1, + "two", + true, + 4.5m, + Guid.Parse("11111111-1111-1111-1111-111111111111"), + 6L, + new DateTime(2026, 5, 27, 0, 0, 0, DateTimeKind.Utc) + ]; + + var result = source.Partition(); + + result.First.Should().Equal(1); + result.Second.Should().Equal("two"); + result.Third.Should().Equal(true); + result.Fourth.Should().Equal(4.5m); + result.Fifth.Should().Equal(Guid.Parse("11111111-1111-1111-1111-111111111111")); + result.Sixth.Should().Equal(6L); + result.Seventh.Should().Equal(new DateTime(2026, 5, 27, 0, 0, 0, DateTimeKind.Utc)); + } + + [Fact] + public void Traverse_T8_ShouldProjectAndPartition() + { + int[] source = [0, 1, 2, 3, 4, 5, 6, 7]; + + var result = source.Traverse(value => value switch + { + 0 => value, + 1 => $"item-{value}", + 2 => true, + 3 => 3.5d, + 4 => 4.5m, + 5 => 5L, + 6 => Guid.Parse("11111111-1111-1111-1111-111111111111"), + _ => new DateTime(2026, 5, 27, 0, 0, 0, DateTimeKind.Utc) + }); + + result.First.Should().Equal(0); + result.Second.Should().Equal("item-1"); + result.Third.Should().Equal(true); + result.Fourth.Should().Equal(3.5d); + result.Fifth.Should().Equal(4.5m); + result.Sixth.Should().Equal(5L); + result.Seventh.Should().Equal(Guid.Parse("11111111-1111-1111-1111-111111111111")); + result.Eighth.Should().Equal(new DateTime(2026, 5, 27, 0, 0, 0, DateTimeKind.Utc)); + } + + [Fact] + public void Partition_WithEmptySource_ShouldReturnEmptyArrays() + { + var result = Array.Empty>().Partition(); + + result.First.Should().BeEmpty(); + result.Second.Should().BeEmpty(); + } + + [Fact] + public void CollectionExtensions_WithNullSource_ShouldThrowArgumentNullException() + { + IEnumerable> any2 = null!; + IEnumerable> any8 = null!; + IEnumerable values = null!; + + Action[] actions = + [ + () => any2.Partition(), + () => any8.Partition(), + () => values.Traverse(value => value), + () => values.Traverse(value => value switch + { + 0 => value, + 1 => value.ToString(CultureInfo.InvariantCulture), + 2 => true, + 3 => 3.5m, + 4 => Guid.Empty, + 5 => 5L, + 6 => DateTime.UnixEpoch, + _ => TimeSpan.Zero + }) + ]; + + foreach (Action action in actions) + action.Should().Throw().WithParameterName("source"); + } + + [Fact] + public void Traverse_WithNullSelector_ShouldThrowArgumentNullException() + { + int[] values = [1]; + Func> selector = null!; + + Action action = () => values.Traverse(selector); + + action.Should().Throw().WithParameterName("selector"); + } +} diff --git a/CSharpEssentials.Tests/Maybe/MaybeCollectionExtensionsTests.cs b/CSharpEssentials.Tests/Maybe/MaybeCollectionExtensionsTests.cs new file mode 100644 index 0000000..cd741f7 --- /dev/null +++ b/CSharpEssentials.Tests/Maybe/MaybeCollectionExtensionsTests.cs @@ -0,0 +1,90 @@ +using CSharpEssentials.Maybe; +using FluentAssertions; + +namespace CSharpEssentials.Tests.Maybe; + +public class MaybeCollectionExtensionsTests +{ + [Fact] + public void Sequence_AllValues_ShouldReturnMaybeArray() + { + Maybe[] source = [1, 2, 3]; + + Maybe result = source.Sequence(); + + result.HasValue.Should().BeTrue(); + result.Value.Should().Equal(1, 2, 3); + } + + [Fact] + public void Sequence_WithNone_ShouldReturnNone() + { + Maybe[] source = [1, Maybe.None, 3]; + + Maybe result = source.Sequence(); + + result.HasNoValue.Should().BeTrue(); + } + + [Fact] + public void Traverse_ShouldProjectAndSequence() + { + int[] source = [1, 2, 3]; + + Maybe result = source.Traverse(value => Maybe.From($"item-{value}")); + + result.HasValue.Should().BeTrue(); + result.Value.Should().Equal("item-1", "item-2", "item-3"); + } + + [Fact] + public void Partition_ShouldReturnValuesAndNoneCount() + { + Maybe[] source = [1, Maybe.None, 3, Maybe.None]; + + (int[] values, int noneCount) = source.Partition(); + + values.Should().Equal(1, 3); + noneCount.Should().Be(2); + } + + [Fact] + public void CollectionExtensions_WithEmptySource_ShouldReturnEmptyValues() + { + Maybe sequence = Array.Empty>().Sequence(); + (int[] values, int noneCount) = Array.Empty>().Partition(); + + sequence.HasValue.Should().BeTrue(); + sequence.Value.Should().BeEmpty(); + values.Should().BeEmpty(); + noneCount.Should().Be(0); + } + + [Fact] + public void CollectionExtensions_WithNullSource_ShouldThrowArgumentNullException() + { + IEnumerable> maybes = null!; + IEnumerable values = null!; + + Action[] actions = + [ + () => maybes.Sequence(), + () => maybes.Partition(), + () => values.Traverse(value => Maybe.From(value)) + ]; + + foreach (Action action in actions) + action.Should().Throw().WithParameterName("source"); + } + + [Fact] + public void Traverse_WithNullSelector_ShouldThrowArgumentNullException() + { + int[] values = [1]; + Func> selector = null!; + + Action action = () => values.Traverse(selector); + + action.Should().Throw().WithParameterName("selector"); + } +} diff --git a/CSharpEssentials.Tests/Results/ResultCollectionBatchTests.cs b/CSharpEssentials.Tests/Results/ResultCollectionBatchTests.cs new file mode 100644 index 0000000..61a8804 --- /dev/null +++ b/CSharpEssentials.Tests/Results/ResultCollectionBatchTests.cs @@ -0,0 +1,229 @@ +using CSharpEssentials.Errors; +using CSharpEssentials.ResultPattern; +using CSharpEssentials.ResultPattern.Interfaces; +using FluentAssertions; + +namespace CSharpEssentials.Tests.Results; + +public class ResultCollectionBatchTests +{ + [Fact] + public void Sequence_AllSuccesses_ShouldReturnValues() + { + Result[] source = [1, 2, 3]; + + Result result = source.Sequence(); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Equal(1, 2, 3); + } + + [Fact] + public void Sequence_WithFailures_ShouldAggregateErrors() + { + Result[] source = + [ + 1, + Error.Validation("First.Error", "First"), + Error.Validation("Second.Error", "Second") + ]; + + Result result = source.Sequence(); + + result.IsFailure.Should().BeTrue(); + result.Errors.Select(x => x.Code).Should().Equal("First.Error", "Second.Error"); + } + + [Fact] + public void Traverse_ShouldProjectAndSequence() + { + int[] source = [1, 2, 3]; + + Result result = source.Traverse(value => $"item-{value}".ToResult()); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Equal("item-1", "item-2", "item-3"); + } + + [Fact] + public void Partition_ShouldReturnSuccessesAndFlattenedErrors() + { + Result[] source = + [ + 10, + Error.Validation("First.Error", "First"), + 20, + Error.Validation("Second.Error", "Second") + ]; + + (int[] successes, Error[] errors) = source.Partition(); + + successes.Should().Equal(10, 20); + errors.Select(x => x.Code).Should().Equal("First.Error", "Second.Error"); + } + + [Fact] + public void CombineAll_NonGeneric_ShouldAggregateErrors() + { + Result[] source = + [ + Result.Success(), + Error.Validation("First.Error", "First"), + Error.Validation("Second.Error", "Second") + ]; + + Result result = source.CombineAll(); + + result.IsFailure.Should().BeTrue(); + result.Errors.Select(x => x.Code).Should().Equal("First.Error", "Second.Error"); + } + + [Fact] + public void CombineAll_IResultBase_ShouldAggregateErrors() + { + IResultBase[] source = + [ + Result.Success(), + Result.Failure(Error.Validation("First.Error", "First")), + Result.Failure(Error.Validation("Second.Error", "Second")) + ]; + + Result result = source.CombineAll(); + + result.IsFailure.Should().BeTrue(); + result.Errors.Select(x => x.Code).Should().Equal("First.Error", "Second.Error"); + } + + [Fact] + public void CombineAll_WithEmptyCollections_ShouldReturnSuccess() + { + Array.Empty().CombineAll().IsSuccess.Should().BeTrue(); + Array.Empty().CombineAll().IsSuccess.Should().BeTrue(); + Array.Empty>().CombineAll().Value.Should().BeEmpty(); + Array.Empty>().CombineAll().Value.Should().BeEmpty(); + } + + [Fact] + public void FirstFailureOrSuccesses_WithSuccesses_ShouldReturnValues() + { + Result[] source = [1, 2, 3]; + + Result result = source.FirstFailureOrSuccesses(); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Equal(1, 2, 3); + } + + [Fact] + public void Sequence_IResultOverload_ShouldReturnValues() + { + IResult[] source = [Result.Success(1), Result.Success(2), Result.Success(3)]; + + Result result = source.Sequence(); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Equal(1, 2, 3); + } + + [Fact] + public void Partition_IResultOverload_ShouldReturnSuccessesAndFlattenedErrors() + { + IResult[] source = + [ + Result.Success(10), + Result.Failure(Error.Validation("First.Error", "First")), + Result.Success(20), + Result.Failure(Error.Validation("Second.Error", "Second")) + ]; + + (int[] successes, Error[] errors) = source.Partition(); + + successes.Should().Equal(10, 20); + errors.Select(x => x.Code).Should().Equal("First.Error", "Second.Error"); + } + + [Fact] + public void FirstFailureOrSuccesses_IResultOverload_WithSuccesses_ShouldReturnValues() + { + IResult[] source = [Result.Success(1), Result.Success(2), Result.Success(3)]; + + Result result = source.FirstFailureOrSuccesses(); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Equal(1, 2, 3); + } + + [Fact] + public void FirstFailureOrSuccesses_ShouldShortCircuitOnFirstFailure() + { + static IEnumerable> Source() + { + yield return 1; + yield return Error.Validation("Stop.Error", "Stop"); + throw new InvalidOperationException("Enumeration should stop at first failure."); + } + + Result result = Source().FirstFailureOrSuccesses(); + + result.IsFailure.Should().BeTrue(); + result.Errors.Should().ContainSingle(x => x.Code == "Stop.Error"); + } + + [Fact] + public void ResultCollectionExtensions_WithEmptyCollections_ShouldReturnEmptySuccesses() + { + Array.Empty>().Sequence().Value.Should().BeEmpty(); + Array.Empty>().Sequence().Value.Should().BeEmpty(); + Array.Empty>().FirstFailureOrSuccesses().Value.Should().BeEmpty(); + Array.Empty>().FirstFailureOrSuccesses().Value.Should().BeEmpty(); + + (int[] structValues, Error[] structErrors) = Array.Empty>().Partition(); + (int[] interfaceValues, Error[] interfaceErrors) = Array.Empty>().Partition(); + + structValues.Should().BeEmpty(); + structErrors.Should().BeEmpty(); + interfaceValues.Should().BeEmpty(); + interfaceErrors.Should().BeEmpty(); + } + + [Fact] + public void ResultCollectionExtensions_WithNullSource_ShouldThrowArgumentNullException() + { + IEnumerable results = null!; + IEnumerable resultBases = null!; + IEnumerable> genericResults = null!; + IEnumerable> genericInterfaces = null!; + IEnumerable values = null!; + + Action[] actions = + [ + () => results.CombineAll(), + () => resultBases.CombineAll(), + () => genericResults.CombineAll(), + () => genericInterfaces.CombineAll(), + () => genericResults.Sequence(), + () => genericInterfaces.Sequence(), + () => genericResults.Partition(), + () => genericInterfaces.Partition(), + () => results.FirstFailureOrSuccesses(), + () => resultBases.FirstFailureOrSuccesses(), + () => genericResults.FirstFailureOrSuccesses(), + () => genericInterfaces.FirstFailureOrSuccesses(), + () => values.Traverse(value => value.ToResult()) + ]; + + foreach (Action action in actions) + action.Should().Throw().WithParameterName("source"); + } + + [Fact] + public void Traverse_WithNullSelector_ShouldThrowArgumentNullException() + { + int[] values = [1]; + Func> selector = null!; + + Action action = () => values.Traverse(selector); + + action.Should().Throw().WithParameterName("selector"); + } +} diff --git a/README.MD b/README.MD index c8cea3a..6db0720 100644 --- a/README.MD +++ b/README.MD @@ -5,7 +5,10 @@ # 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-2749%20passing-brightgreen)](https://github.com/senrecep/CSharpEssentials/actions/workflows/build.yml) + +[![Tests](https://img.shields.io/badge/tests-2781%20passing-brightgreen)](https://github.com/senrecep/CSharpEssentials/actions/workflows/build.yml) +======= +>>>>>>> pr-21 [![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)