Skip to content

Commit

Permalink
Collection expression support added
Browse files Browse the repository at this point in the history
  • Loading branch information
zbyszekprasak committed Dec 18, 2024
1 parent ff7eac7 commit bc1c2f1
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 5 deletions.
7 changes: 2 additions & 5 deletions src/ErrorOr.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,11 @@
<AdditionalFiles Include="Stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference
Include="Microsoft.Bcl.HashCode"
Version="1.1.1"
Condition="'$(TargetFramework)' == 'netstandard2.0'"
/>
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
<PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Memory" Version="4.6.0" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
</ItemGroup>
</Project>
15 changes: 15 additions & 0 deletions src/ErrorOr/CollectionBuilderAttribute.cs
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions src/ErrorOr/CollectionExpression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace ErrorOr;

/// <summary>
/// Contains methods supporting collection expressions.
/// </summary>
public static class CollectionExpression
{
/// <summary>
/// Creates <see cref="ErrorOr{TValue}"/> from read-only span of errors.
/// </summary>
/// <typeparam name="TValue">Type of value.</typeparam>
/// <param name="errors">Read-only span of errors.</param>
/// <returns>Error or vale.</returns>
/// <remarks>Enables support for collection expressions.</remarks>
public static ErrorOr<TValue> CreateErrorOr<TValue>(ReadOnlySpan<Error> errors)
{
return errors.ToArray();
}

/// <summary>
/// Creates <see cref="IErrorOr{TValue}"/> from read-only span of errors.
/// </summary>
/// <typeparam name="TValue">Type of value.</typeparam>
/// <param name="errors">Read-only span of errors.</param>
/// <returns>Error or vale.</returns>
/// <remarks>Enables support for collection expressions.</remarks>
public static IErrorOr<TValue> CreateIErrorOrValue<TValue>(ReadOnlySpan<Error> errors)
{
return CreateErrorOr<TValue>(errors);
}

/// <summary>
/// Creates <see cref="IErrorOr"/> from read-only span of errors.
/// </summary>
/// <param name="errors">Read-only span of errors.</param>
/// <returns>Error or vale.</returns>
/// <remarks>Enables support for collection expressions.</remarks>
public static IErrorOr CreateIErrorOr(ReadOnlySpan<Error> errors)
{
return CreateErrorOr<object>(errors);
}
}
5 changes: 5 additions & 0 deletions src/ErrorOr/ErrorOr.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace ErrorOr;

/// <summary>
/// A discriminated union of errors or a value.
/// </summary>
/// <typeparam name="TValue">The type of the underlying <see cref="Value"/>.</typeparam>
[CollectionBuilder(typeof(CollectionExpression), nameof(CollectionExpression.CreateErrorOr))]
public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
private readonly TValue? _value = default;
Expand Down Expand Up @@ -104,6 +106,9 @@ public Error FirstError
}
}

/// <inheritdoc/>
public IEnumerator<Error> GetEnumerator() => _errors!.GetEnumerator();

/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a list of errors.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions src/ErrorOr/IErrorOr.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Runtime.CompilerServices;

namespace ErrorOr;

[CollectionBuilder(typeof(CollectionExpression), nameof(CollectionExpression.CreateIErrorOrValue))]
public interface IErrorOr<out TValue> : IErrorOr
{
/// <summary>
Expand All @@ -14,6 +17,7 @@ public interface IErrorOr<out TValue> : IErrorOr
/// <remarks>
/// This interface is intended for use when the underlying type of the <see cref="ErrorOr"/> object is unknown.
/// </remarks>
[CollectionBuilder(typeof(CollectionExpression), nameof(CollectionExpression.CreateIErrorOr))]
public interface IErrorOr
{
/// <summary>
Expand All @@ -25,4 +29,11 @@ public interface IErrorOr
/// Gets a value indicating whether the state is error.
/// </summary>
bool IsError { get; }

/// <summary>
/// Gets enumerator with <see cref="Error"/> objects.
/// </summary>
/// <returns>Enunerator of <see cref="Error"/> objects.</returns>
/// <remarks>This method is only for the purpose of collection expression support.</remarks>
IEnumerator<Error> GetEnumerator();
}
45 changes: 45 additions & 0 deletions tests/ErrorOr/ErrorOr.InstantiationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,4 +407,49 @@ public void CreateErrorOr_WhenValueIsNull_ShouldThrowArgumentNullException()
act.Should().ThrowExactly<ArgumentNullException>()
.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<Person> 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<Person> 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]);
}
}

0 comments on commit bc1c2f1

Please sign in to comment.