Skip to content

Commit

Permalink
Restrict Value to be non-nullable
Browse files Browse the repository at this point in the history
  • Loading branch information
amantinband committed May 12, 2024
1 parent 7f4bbe3 commit fcc4099
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 161 deletions.
105 changes: 45 additions & 60 deletions src/ErrorOr/ErrorOr.ImplicitConverters.cs
Original file line number Diff line number Diff line change
@@ -1,60 +1,45 @@
namespace ErrorOr;

public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a value.
/// </summary>
public static implicit operator ErrorOr<TValue>(TValue value)
{
return new ErrorOr<TValue>(value);
}

/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from an error.
/// </summary>
public static implicit operator ErrorOr<TValue>(Error error)
{
return new ErrorOr<TValue>(error);
}

/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a list of errors.
/// </summary>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="errors"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="errors" /> is an empty list.</exception>
public static implicit operator ErrorOr<TValue>(List<Error> errors)
{
if (errors is null)
{
throw new ArgumentNullException(nameof(errors));
}

if (errors.Count == 0)
{
throw new ArgumentException("Cannot create an ErrorOr<TValue> from an empty list of errors. Provide at least one error.", nameof(errors));
}

return new ErrorOr<TValue>(errors);
}

/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a list of errors.
/// </summary>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="errors"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="errors" /> is an empty array.</exception>
public static implicit operator ErrorOr<TValue>(Error[] errors)
{
if (errors is null)
{
throw new ArgumentNullException(nameof(errors));
}

if (errors.Length == 0)
{
throw new ArgumentException("Cannot create an ErrorOr<TValue> from an empty array of errors. Provide at least one error.", nameof(errors));
}

return new ErrorOr<TValue>(errors.ToList());
}
}
namespace ErrorOr;

public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a value.
/// </summary>
public static implicit operator ErrorOr<TValue>(TValue value)
{
return new ErrorOr<TValue>(value);
}

/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from an error.
/// </summary>
public static implicit operator ErrorOr<TValue>(Error error)
{
return new ErrorOr<TValue>(error);
}

/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a list of errors.
/// </summary>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="errors"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="errors" /> is an empty list.</exception>
public static implicit operator ErrorOr<TValue>(List<Error> errors)
{
return new ErrorOr<TValue>(errors);
}

/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a list of errors.
/// </summary>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="errors"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="errors" /> is an empty array.</exception>
public static implicit operator ErrorOr<TValue>(Error[] errors)
{
if (errors is null)
{
throw new ArgumentNullException(nameof(errors));
}

return new ErrorOr<TValue>([.. errors]);
}
}
213 changes: 114 additions & 99 deletions src/ErrorOr/ErrorOr.cs
Original file line number Diff line number Diff line change
@@ -1,99 +1,114 @@
using System.Diagnostics.CodeAnalysis;

namespace ErrorOr;

/// <summary>
/// A discriminated union of errors or a value.
/// </summary>
/// <typeparam name="TValue">The type of the underlying <see cref="Value"/>.</typeparam>
public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
private readonly TValue? _value = default;
private readonly List<Error>? _errors = null;

/// <summary>
/// Prevents a default <see cref="ErrorOr"/> struct from being created.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when this method is called.</exception>
public ErrorOr()
{
throw new InvalidOperationException("Default construction of ErrorOr<TValue> is invalid. Please use provided factory methods to instantiate.");
}

private ErrorOr(Error error)
{
_errors = [error];
}

private ErrorOr(List<Error> errors)
{
_errors = errors;
}

private ErrorOr(TValue value)
{
_value = value;
}

/// <summary>
/// Gets a value indicating whether the state is error.
/// </summary>
[MemberNotNullWhen(true, nameof(_errors))]
[MemberNotNullWhen(true, nameof(Errors))]
[MemberNotNullWhen(false, nameof(Value))]
[MemberNotNullWhen(false, nameof(_value))]
public bool IsError => _errors is not null;

/// <summary>
/// Gets the list of errors. If the state is not error, the list will contain a single error representing the state.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when no errors are present.</exception>
public List<Error> Errors => IsError ? _errors : throw new InvalidOperationException("The Errors property cannot be accessed when no errors have been recorded. Check IsError before accessing Errors.");

/// <summary>
/// Gets the list of errors. If the state is not error, the list will be empty.
/// </summary>
public List<Error> ErrorsOrEmptyList => IsError ? _errors : EmptyErrors.Instance;

/// <summary>
/// Gets the value.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when no value is present.</exception>
public TValue Value
{
get
{
if (IsError)
{
throw new InvalidOperationException("The Value property cannot be accessed when errors have been recorded. Check IsError before accessing Value.");
}

return _value;
}
}

/// <summary>
/// Gets the first error.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when no errors are present.</exception>
public Error FirstError
{
get
{
if (!IsError)
{
throw new InvalidOperationException("The FirstError property cannot be accessed when no errors have been recorded. Check IsError before accessing FirstError.");
}

return _errors[0];
}
}

/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a list of errors.
/// </summary>
public static ErrorOr<TValue> From(List<Error> errors)
{
return errors;
}
}
using System.Diagnostics.CodeAnalysis;

namespace ErrorOr;

/// <summary>
/// A discriminated union of errors or a value.
/// </summary>
/// <typeparam name="TValue">The type of the underlying <see cref="Value"/>.</typeparam>
public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
private readonly TValue? _value = default;
private readonly List<Error>? _errors = null;

/// <summary>
/// Prevents a default <see cref="ErrorOr"/> struct from being created.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when this method is called.</exception>
public ErrorOr()
{
throw new InvalidOperationException("Default construction of ErrorOr<TValue> is invalid. Please use provided factory methods to instantiate.");
}

private ErrorOr(Error error)
{
_errors = [error];
}

private ErrorOr(List<Error> errors)
{
if (errors is null)
{
throw new ArgumentNullException(nameof(errors));
}

if (errors is null || errors.Count == 0)
{
throw new ArgumentException("Cannot create an ErrorOr<TValue> from an empty collection of errors. Provide at least one error.", nameof(errors));
}

_errors = errors;
}

private ErrorOr(TValue value)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}

_value = value;
}

/// <summary>
/// Gets a value indicating whether the state is error.
/// </summary>
[MemberNotNullWhen(true, nameof(_errors))]
[MemberNotNullWhen(true, nameof(Errors))]
[MemberNotNullWhen(false, nameof(Value))]
[MemberNotNullWhen(false, nameof(_value))]
public bool IsError => _errors is not null;

/// <summary>
/// Gets the list of errors. If the state is not error, the list will contain a single error representing the state.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when no errors are present.</exception>
public List<Error> Errors => IsError ? _errors : throw new InvalidOperationException("The Errors property cannot be accessed when no errors have been recorded. Check IsError before accessing Errors.");

/// <summary>
/// Gets the list of errors. If the state is not error, the list will be empty.
/// </summary>
public List<Error> ErrorsOrEmptyList => IsError ? _errors : EmptyErrors.Instance;

/// <summary>
/// Gets the value.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when no value is present.</exception>
public TValue Value
{
get
{
if (IsError)
{
throw new InvalidOperationException("The Value property cannot be accessed when errors have been recorded. Check IsError before accessing Value.");
}

return _value;
}
}

/// <summary>
/// Gets the first error.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when no errors are present.</exception>
public Error FirstError
{
get
{
if (!IsError)
{
throw new InvalidOperationException("The FirstError property cannot be accessed when no errors have been recorded. Check IsError before accessing FirstError.");
}

return _errors[0];
}
}

/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a list of errors.
/// </summary>
public static ErrorOr<TValue> From(List<Error> errors)
{
return errors;
}
}
13 changes: 11 additions & 2 deletions tests/ErrorOr/ErrorOr.InstantiationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ public void CreateErrorOr_WhenEmptyErrorsList_ShouldThrow()

// Assert
var exception = errorOrInt.Should().ThrowExactly<ArgumentException>().Which;
exception.Message.Should().Be("Cannot create an ErrorOr<TValue> from an empty list of errors. Provide at least one error. (Parameter 'errors')");
exception.Message.Should().Be("Cannot create an ErrorOr<TValue> from an empty collection of errors. Provide at least one error. (Parameter 'errors')");
exception.ParamName.Should().Be("errors");
}

Expand All @@ -377,7 +377,7 @@ public void CreateErrorOr_WhenEmptyErrorsArray_ShouldThrow()

// Assert
var exception = errorOrInt.Should().ThrowExactly<ArgumentException>().Which;
exception.Message.Should().Be("Cannot create an ErrorOr<TValue> from an empty array of errors. Provide at least one error. (Parameter 'errors')");
exception.Message.Should().Be("Cannot create an ErrorOr<TValue> from an empty collection of errors. Provide at least one error. (Parameter 'errors')");
exception.ParamName.Should().Be("errors");
}

Expand All @@ -398,4 +398,13 @@ public void CreateErrorOr_WhenNullIsPassedAsErrorsArray_ShouldThrowArgumentNullE
act.Should().ThrowExactly<ArgumentNullException>()
.And.ParamName.Should().Be("errors");
}

[Fact]
public void CreateErrorOr_WhenValueIsNull_ShouldThrowArgumentNullException()
{
Func<ErrorOr<int?>> act = () => default(int?);

act.Should().ThrowExactly<ArgumentNullException>()
.And.ParamName.Should().Be("value");
}
}

0 comments on commit fcc4099

Please sign in to comment.