diff --git a/.editorconfig b/.editorconfig index 04559f5..776b881 100644 --- a/.editorconfig +++ b/.editorconfig @@ -70,3 +70,6 @@ dotnet_diagnostic.SA1642.severity = none # SA1516: Elements should be separated by blank line dotnet_diagnostic.SA1516.severity = none + +[*.csproj] +indent_size = 2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ecd515..c5d53b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,56 +2,123 @@ All notable changes to this project are documented in this file. -## [1.10.0] - 2024-02-14 +## [2.1.0-alpha.0] - to be released ### Added -- `ErrorType.Forbidden` -- README to NuGet package +- [#94](https://github.com/amantinband/error-or/issues/94), [#95](https://github.com/amantinband/error-or/pull/95) Added missing async versions of `FailIf` methods -## [1.9.0] - 2024-01-06 + ```cs + public async Task> FailIfAsync(Func> onValue, Error error) + ``` -### Added + ```cs + public static async Task> FailIf( + this Task> errorOr, + Func onValue, + Error error) + ``` -- `ToErrorOr` + ```cs + public static async Task> FailIfAsync( + this Task> errorOr, + Func> onValue, + Error error) + ``` -## [2.0.0] - 2024-03-26 +- [#104](https://github.com/amantinband/error-or/pull/104) Support for .NET 8 was added -### Added +- [#109](https://github.com/amantinband/error-or/issues/109), [#111](https://github.com/amantinband/error-or/pull/111) Added `FailIf` method overloads that allow to use value in error definition using `Func` error builder -- `FailIf` + ```cs + public ErrorOr FailIf(Func onValue, Func errorBuilder) + ``` + + ```cs + public async Task> FailIfAsync(Func> onValue, Func> errorBuilder) + ``` -```csharp -public ErrorOr FailIf(Func onValue, Error error) -``` + ```cs + public static async Task> FailIf( + this Task> errorOr, + Func onValue, + Func errorBuilder) + ``` -```csharp -ErrorOr errorOr = 1; -errorOr.FailIf(x => x > 0, Error.Failure()); -``` + ```cs + public static async Task> FailIfAsync( + this Task> errorOr, + Func> onValue, + Func> errorBuilder) + ``` + + Value can now be used to build the error: + + ```cs + ErrorOr result = errorOrInt + .FailIf(num => num > 3, (num) => Error.Failure(description: $"{num} is greater than 3")); + ``` + +### Fixed + +- [#85](https://github.com/amantinband/error-or/issues/85), [#97](https://github.com/amantinband/error-or/pull/97) `ErrorOr` turned into Value Object by reimplementing `Equals` and `GetHashCode` methods + + New dependency was introduced to [Microsoft.Bcl.HashCode](https://www.nuget.org/packages/Microsoft.Bcl.HashCode) and development dependency was introduced to [Nullable](https://www.nuget.org/packages/Nullable) + +### Optimized + +- [#98](https://github.com/amantinband/error-or/issues/98), [#99](https://github.com/amantinband/error-or/pull/99) Memory consumption optimized by moving static empty errors lists from generic struct into non-generic class + +## [2.0.1] - 2024-03-26 ### Breaking Changes - `Then` that receives an action is now called `ThenDo` -```diff --public ErrorOr Then(Action action) -+public ErrorOr ThenDo(Action action) -``` + ```diff + -public ErrorOr Then(Action action) + +public ErrorOr ThenDo(Action action) + ``` -```diff --public static async Task> Then(this Task> errorOr, Action action) -+public static async Task> ThenDo(this Task> errorOr, Action action) -``` + ```diff + -public static async Task> Then(this Task> errorOr, Action action) + +public static async Task> ThenDo(this Task> errorOr, Action action) + ``` - `ThenAsync` that receives an action is now called `ThenDoAsync` -```diff --public async Task> ThenAsync(Func action) -+public async Task> ThenDoAsync(Func action) -``` + ```diff + -public async Task> ThenAsync(Func action) + +public async Task> ThenDoAsync(Func action) + ``` + + ```diff + -public static async Task> ThenAsync(this Task> errorOr, Func action) + +public static async Task> ThenDoAsync(this Task> errorOr, Func action) + ``` -```diff --public static async Task> ThenAsync(this Task> errorOr, Func action) -+public static async Task> ThenDoAsync(this Task> errorOr, Func action) -``` +### Added + +- `FailIf` + + ```csharp + public ErrorOr FailIf(Func onValue, Error error) + ``` + + ```csharp + ErrorOr errorOr = 1; + errorOr.FailIf(x => x > 0, Error.Failure()); + ``` + +## [1.10.0] - 2024-02-14 + +### Added + +- `ErrorType.Forbidden` +- README to NuGet package + +## [1.9.0] - 2024-01-06 + +### Added + +- `ToErrorOr` diff --git a/src/ErrorOr.csproj b/src/ErrorOr.csproj index 55ea443..6a7420b 100644 --- a/src/ErrorOr.csproj +++ b/src/ErrorOr.csproj @@ -1,13 +1,14 @@ - netstandard2.0;net8.0 + netstandard2.0;net6.0;net8.0 enable enable true ErrorOr - 2.0.1 + 2.1.0 + alpha.0 Amichai Mantinband icon-square.png Result,Results,ErrorOr,Error,Handling diff --git a/src/ErrorOr/EmptyErrors.cs b/src/ErrorOr/EmptyErrors.cs deleted file mode 100644 index 7a79e64..0000000 --- a/src/ErrorOr/EmptyErrors.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ErrorOr; - -internal static class EmptyErrors -{ - public static List Instance { get; } = []; -} diff --git a/src/ErrorOr/ErrorOr.cs b/src/ErrorOr/ErrorOr.cs index b958157..a473f6f 100644 --- a/src/ErrorOr/ErrorOr.cs +++ b/src/ErrorOr/ErrorOr.cs @@ -11,15 +11,6 @@ namespace ErrorOr; private readonly TValue? _value = default; private readonly List? _errors = null; - /// - /// Prevents a default struct from being created. - /// - /// Thrown when this method is called. - public ErrorOr() - { - throw new InvalidOperationException("Default construction of ErrorOr is invalid. Please use provided factory methods to instantiate."); - } - private ErrorOr(Error error) { _errors = [error]; @@ -62,42 +53,28 @@ private ErrorOr(TValue value) /// /// Gets the list of errors. If the state is not error, the list will contain a single error representing the state. /// - /// Thrown when no errors are present. - public List Errors => IsError ? _errors : throw new InvalidOperationException("The Errors property cannot be accessed when no errors have been recorded. Check IsError before accessing Errors."); + public List Errors => IsError ? _errors : KnownErrors.CachedNoErrorsList; /// /// Gets the list of errors. If the state is not error, the list will be empty. /// - public List ErrorsOrEmptyList => IsError ? _errors : EmptyErrors.Instance; + public List ErrorsOrEmptyList => IsError ? _errors : KnownErrors.CachedEmptyErrorsList; /// /// Gets the value. /// - /// Thrown when no value is present. - 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; - } - } + public TValue Value => _value!; /// /// Gets the first error. /// - /// Thrown when no errors are present. 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 KnownErrors.NoFirstError; } return _errors[0]; diff --git a/src/Errors/KnownErrors.cs b/src/Errors/KnownErrors.cs new file mode 100644 index 0000000..cfa7220 --- /dev/null +++ b/src/Errors/KnownErrors.cs @@ -0,0 +1,16 @@ +namespace ErrorOr; + +internal static class KnownErrors +{ + public static Error NoFirstError { get; } = Error.Unexpected( + code: "ErrorOr.NoFirstError", + description: "First error cannot be retrieved from a successful ErrorOr."); + + public static Error NoErrors { get; } = Error.Unexpected( + code: "ErrorOr.NoErrors", + description: "Error list cannot be retrieved from a successful ErrorOr."); + + public static List CachedNoErrorsList { get; } = new (1) { NoErrors }; + + public static List CachedEmptyErrorsList { get; } = new (0); +} diff --git a/tests/ErrorOr/ErrorOr.InstantiationTests.cs b/tests/ErrorOr/ErrorOr.InstantiationTests.cs index 77d17ca..4b91c0c 100644 --- a/tests/ErrorOr/ErrorOr.InstantiationTests.cs +++ b/tests/ErrorOr/ErrorOr.InstantiationTests.cs @@ -22,17 +22,17 @@ public void CreateFromFactory_WhenAccessingValue_ShouldReturnValue() } [Fact] - public void CreateFromFactory_WhenAccessingErrors_ShouldThrow() + public void CreateFromFactory_WhenAccessingErrors_ShouldReturnUnexpectedError() { // Arrange IEnumerable value = ["value"]; ErrorOr> errorOrPerson = ErrorOrFactory.From(value); // Act - Func> errors = () => errorOrPerson.Errors; + List errors = errorOrPerson.Errors; // Assert - errors.Should().ThrowExactly(); + errors.Should().ContainSingle().Which.Type.Should().Be(ErrorType.Unexpected); } [Fact] @@ -50,17 +50,17 @@ public void CreateFromFactory_WhenAccessingErrorsOrEmptyList_ShouldReturnEmptyLi } [Fact] - public void CreateFromFactory_WhenAccessingFirstError_ShouldThrow() + public void CreateFromFactory_WhenAccessingFirstError_ShouldReturnUnexpectedError() { // Arrange IEnumerable value = ["value"]; ErrorOr> errorOrPerson = ErrorOrFactory.From(value); // Act - Func action = () => errorOrPerson.FirstError; + Error firstError = errorOrPerson.FirstError; // Assert - action.Should().ThrowExactly(); + firstError.Type.Should().Be(ErrorType.Unexpected); } [Fact] @@ -78,17 +78,17 @@ public void CreateFromValue_WhenAccessingValue_ShouldReturnValue() } [Fact] - public void CreateFromValue_WhenAccessingErrors_ShouldThrow() + public void CreateFromValue_WhenAccessingErrors_ShouldReturnUnexpectedError() { // Arrange IEnumerable value = ["value"]; ErrorOr> errorOrPerson = ErrorOrFactory.From(value); // Act - Func> action = () => errorOrPerson.Errors; + List errors = errorOrPerson.Errors; // Assert - action.Should().ThrowExactly(); + errors.Should().ContainSingle().Which.Type.Should().Be(ErrorType.Unexpected); } [Fact] @@ -106,17 +106,17 @@ public void CreateFromValue_WhenAccessingErrorsOrEmptyList_ShouldReturnEmptyList } [Fact] - public void CreateFromValue_WhenAccessingFirstError_ShouldThrow() + public void CreateFromValue_WhenAccessingFirstError_ShouldReturnUnexpectedError() { // Arrange IEnumerable value = ["value"]; ErrorOr> errorOrPerson = ErrorOrFactory.From(value); // Act - Func action = () => errorOrPerson.FirstError; + Error firstError = errorOrPerson.FirstError; // Assert - action.Should().ThrowExactly(); + firstError.Type.Should().Be(ErrorType.Unexpected); } [Fact] @@ -144,18 +144,17 @@ public void CreateFromErrorList_WhenAccessingErrorsOrEmptyList_ShouldReturnError } [Fact] - public void CreateFromErrorList_WhenAccessingValue_ShouldThrowInvalidOperationException() + public void CreateFromErrorList_WhenAccessingValue_ShouldReturnDefault() { // Arrange List errors = new() { Error.Validation("User.Name", "Name is too short") }; ErrorOr errorOrPerson = ErrorOr.From(errors); // Act - var act = () => errorOrPerson.Value; + Person value = errorOrPerson.Value; // Assert - act.Should().Throw() - .And.Message.Should().Be("The Value property cannot be accessed when errors have been recorded. Check IsError before accessing Value."); + value.Should().Be(default); } [Fact] @@ -173,27 +172,27 @@ public void ImplicitCastResult_WhenAccessingResult_ShouldReturnValue() } [Fact] - public void ImplicitCastResult_WhenAccessingErrors_ShouldThrow() + public void ImplicitCastResult_WhenAccessingErrors_ShouldReturnUnexpectedError() { ErrorOr errorOrPerson = new Person("Amichai"); // Act - Func> action = () => errorOrPerson.Errors; + List errors = errorOrPerson.Errors; // Assert - action.Should().ThrowExactly(); + errors.Should().ContainSingle().Which.Type.Should().Be(ErrorType.Unexpected); } [Fact] - public void ImplicitCastResult_WhenAccessingFirstError_ShouldThrow() + public void ImplicitCastResult_WhenAccessingFirstError_ShouldReturnUnexpectedError() { ErrorOr errorOrPerson = new Person("Amichai"); // Act - Func action = () => errorOrPerson.FirstError; + Error firstError = errorOrPerson.FirstError; // Assert - action.Should().ThrowExactly(); + firstError.Type.Should().Be(ErrorType.Unexpected); } [Fact] @@ -248,17 +247,16 @@ public void ImplicitCastSingleError_WhenAccessingErrors_ShouldReturnErrorList() } [Fact] - public void ImplicitCastError_WhenAccessingValue_ShouldThrowInvalidOperationException() + public void ImplicitCastError_WhenAccessingValue_ShouldReturnDefault() { // Arrange ErrorOr errorOrPerson = Error.Validation("User.Name", "Name is too short"); // Act - var act = () => errorOrPerson.Value; + Person value = errorOrPerson.Value; // Assert - act.Should().Throw() - .And.Message.Should().Be("The Value property cannot be accessed when errors have been recorded. Check IsError before accessing Value."); + value.Should().Be(default); } [Fact] @@ -347,16 +345,6 @@ public void ImplicitCastErrorArray_WhenAccessingFirstError_ShouldReturnFirstErro errorOrPerson.FirstError.Should().Be(errors[0]); } - [Fact] - public void CreateErrorOr_WhenUsingEmptyConstructor_ShouldThrow() - { - // Act - Func> action = () => new ErrorOr(); - - // Assert - action.Should().ThrowExactly(); - } - [Fact] public void CreateErrorOr_WhenEmptyErrorsList_ShouldThrow() { diff --git a/tests/Tests.csproj b/tests/Tests.csproj index 8170f9a..594def7 100644 --- a/tests/Tests.csproj +++ b/tests/Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net6.0;net8.0 enable enable