diff --git a/src/ErrorOr.csproj b/src/ErrorOr.csproj index 55ea443..2d4a2dc 100644 --- a/src/ErrorOr.csproj +++ b/src/ErrorOr.csproj @@ -26,14 +26,11 @@ - + all runtime; build; native; contentfiles; analyzers + diff --git a/src/ErrorOr/CollectionBuilderAttribute.cs b/src/ErrorOr/CollectionBuilderAttribute.cs new file mode 100644 index 0000000..6d8e898 --- /dev/null +++ b/src/ErrorOr/CollectionBuilderAttribute.cs @@ -0,0 +1,15 @@ +#if !NET8_0_OR_GREATER +namespace System.Runtime.CompilerServices; + +internal sealed class CollectionBuilderAttribute : Attribute +{ + public CollectionBuilderAttribute(Type builderType, string methodName) + { + BuilderType = builderType; + MethodName = methodName; + } + + public Type BuilderType { get; } + public string MethodName { get; } +} +#endif diff --git a/src/ErrorOr/CollectionExpression.cs b/src/ErrorOr/CollectionExpression.cs new file mode 100644 index 0000000..f4a5838 --- /dev/null +++ b/src/ErrorOr/CollectionExpression.cs @@ -0,0 +1,42 @@ +namespace ErrorOr; + +/// +/// Contains methods supporting collection expressions. +/// +public static class CollectionExpression +{ + /// + /// Creates from read-only span of errors. + /// + /// Type of value. + /// Read-only span of errors. + /// Error or vale. + /// Enables support for collection expressions. + public static ErrorOr CreateErrorOr(ReadOnlySpan errors) + { + return errors.ToArray(); + } + + /// + /// Creates from read-only span of errors. + /// + /// Type of value. + /// Read-only span of errors. + /// Error or vale. + /// Enables support for collection expressions. + public static IErrorOr CreateIErrorOrValue(ReadOnlySpan errors) + { + return CreateErrorOr(errors); + } + + /// + /// Creates from read-only span of errors. + /// + /// Read-only span of errors. + /// Error or vale. + /// Enables support for collection expressions. + public static IErrorOr CreateIErrorOr(ReadOnlySpan errors) + { + return CreateErrorOr(errors); + } +} diff --git a/src/ErrorOr/ErrorOr.cs b/src/ErrorOr/ErrorOr.cs index b958157..17821ba 100644 --- a/src/ErrorOr/ErrorOr.cs +++ b/src/ErrorOr/ErrorOr.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace ErrorOr; @@ -6,6 +7,7 @@ namespace ErrorOr; /// A discriminated union of errors or a value. /// /// The type of the underlying . +[CollectionBuilder(typeof(CollectionExpression), nameof(CollectionExpression.CreateErrorOr))] public readonly partial record struct ErrorOr : IErrorOr { private readonly TValue? _value = default; @@ -104,6 +106,9 @@ public Error FirstError } } + /// + public IEnumerator GetEnumerator() => _errors!.GetEnumerator(); + /// /// Creates an from a list of errors. /// diff --git a/src/ErrorOr/IErrorOr.cs b/src/ErrorOr/IErrorOr.cs index dc9943f..83b2265 100644 --- a/src/ErrorOr/IErrorOr.cs +++ b/src/ErrorOr/IErrorOr.cs @@ -1,5 +1,8 @@ +using System.Runtime.CompilerServices; + namespace ErrorOr; +[CollectionBuilder(typeof(CollectionExpression), nameof(CollectionExpression.CreateIErrorOrValue))] public interface IErrorOr : IErrorOr { /// @@ -14,6 +17,7 @@ public interface IErrorOr : IErrorOr /// /// This interface is intended for use when the underlying type of the object is unknown. /// +[CollectionBuilder(typeof(CollectionExpression), nameof(CollectionExpression.CreateIErrorOr))] public interface IErrorOr { /// @@ -25,4 +29,11 @@ public interface IErrorOr /// Gets a value indicating whether the state is error. /// bool IsError { get; } + + /// + /// Gets enumerator with objects. + /// + /// Enunerator of objects. + /// This method is only for the purpose of collection expression support. + IEnumerator GetEnumerator(); } diff --git a/tests/ErrorOr/ErrorOr.InstantiationTests.cs b/tests/ErrorOr/ErrorOr.InstantiationTests.cs index 77d17ca..9a8d8c8 100644 --- a/tests/ErrorOr/ErrorOr.InstantiationTests.cs +++ b/tests/ErrorOr/ErrorOr.InstantiationTests.cs @@ -407,4 +407,49 @@ public void CreateErrorOr_WhenValueIsNull_ShouldThrowArgumentNullException() act.Should().ThrowExactly() .And.ParamName.Should().Be("value"); } + + [Fact] + public void ErrorOr_FromErrorCollectionExpression_WhenAccessingErrors_ShouldReturnErrorList() + { + // Arrange + var nameTooShort = Error.Validation("User.Name", "Name is too short"); + var userTooYoung = Error.Validation("User.Age", "User is too young"); + + // Act + ErrorOr errorOrPerson = [nameTooShort, userTooYoung]; + + // Assert + errorOrPerson.IsError.Should().BeTrue(); + errorOrPerson.Errors.Should().HaveCount(2).And.BeEquivalentTo([nameTooShort, userTooYoung]); + } + + [Fact] + public void GenericErrorOrInterface_FromErrorCollectionExpression_WhenAccessingErrors_ShouldReturnErrorList() + { + // Arrange + var nameTooShort = Error.Validation("User.Name", "Name is too short"); + var userTooYoung = Error.Validation("User.Age", "User is too young"); + + // Act + IErrorOr errorOrPerson = [nameTooShort, userTooYoung]; + + // Assert + errorOrPerson.IsError.Should().BeTrue(); + errorOrPerson.Errors.Should().HaveCount(2).And.BeEquivalentTo([nameTooShort, userTooYoung]); + } + + [Fact] + public void ErrorOrInterface_FromErrorCollectionExpression_WhenAccessingErrors_ShouldReturnErrorList() + { + // Arrange + var nameTooShort = Error.Validation("User.Name", "Name is too short"); + var userTooYoung = Error.Validation("User.Age", "User is too young"); + + // Act + IErrorOr errorOrPerson = [nameTooShort, userTooYoung]; + + // Assert + errorOrPerson.IsError.Should().BeTrue(); + errorOrPerson.Errors.Should().HaveCount(2).And.BeEquivalentTo([nameTooShort, userTooYoung]); + } }