From aef177027ea3adc94bf4dcb795456f9f726e5b7e Mon Sep 17 00:00:00 2001 From: Antonello Provenzano Date: Sat, 6 Jul 2024 09:55:27 +0200 Subject: [PATCH] Improving code coverage and documentation --- .../OperationResultExtensions.cs | 54 +++++++++++++++++++ src/Deveel.Results/OperationResult_T.cs | 36 +++++++++++++ .../OperationErrorTests.cs | 22 ++++++++ .../OperationResultOfTTests.cs | 18 +++++++ .../OperationResultTests.cs | 15 ++++++ 5 files changed, 145 insertions(+) diff --git a/src/Deveel.Results/OperationResultExtensions.cs b/src/Deveel.Results/OperationResultExtensions.cs index 339f579..2d7416f 100644 --- a/src/Deveel.Results/OperationResultExtensions.cs +++ b/src/Deveel.Results/OperationResultExtensions.cs @@ -88,6 +88,33 @@ public static bool HasValidationErrors(this IOperationResult result) public static IReadOnlyList ValidationResults(this IOperationResult result) => result.HasValidationErrors() ? ((IValidationError)result.Error!).ValidationResults : Array.Empty(); + /// + /// Attempts to match the operation result to a specific state + /// that can be handled by the caller. + /// + /// + /// The type of the result that is returned by the match. + /// + /// + /// The operation result to match. + /// + /// + /// A function that is called when the operation result was a success. + /// + /// + /// A function that is called when the operation result was an error. + /// + /// + /// A function that is called when the operation result caused no changed + /// to the object. + /// + /// + /// Returns the result of the function that was called based on the state + /// of the operation result. + /// + /// + /// Thrown when the operation result is in an unknown state. + /// public static TResult Match(this IOperationResult result, Func? ifSuccess = null, Func? ifError = null, @@ -116,6 +143,33 @@ public static TResult Match(this IOperationResult result, throw new InvalidOperationException("The operation result is in an unknown state."); } + /// + /// Attempts to match the operation result to a specific state + /// that can be handled by the caller. + /// + /// + /// The type of the result that is returned by the match. + /// + /// + /// The operation result to match. + /// + /// + /// A function that is called when the operation result was a success. + /// + /// + /// A function that is called when the operation result was an error. + /// + /// + /// A function that is called when the operation result caused no changed + /// to the object. + /// + /// + /// Returns the result of the function that was called based on the state + /// of the operation result. + /// + /// + /// Thrown when the operation result is in an unknown state. + /// public static Task MatchAsync(this IOperationResult result, Func>? ifSuccess = null, Func>? ifError = null, diff --git a/src/Deveel.Results/OperationResult_T.cs b/src/Deveel.Results/OperationResult_T.cs index 5cfe8fc..8701e79 100644 --- a/src/Deveel.Results/OperationResult_T.cs +++ b/src/Deveel.Results/OperationResult_T.cs @@ -2,6 +2,12 @@ namespace Deveel { + /// + /// A result of an operation that has a value of a specific type. + /// + /// + /// The type of the value that represents the result of the operation. + /// public readonly struct OperationResult : IOperationResult { private OperationResult(OperationResultType resultType, T? value, IOperationError? error) @@ -111,11 +117,41 @@ public static OperationResult Fail(string code, string domain, string? messag public static OperationResult ValidationFailed(string code, string domain, IEnumerable results) => Fail(new OperationValidationError(code, domain, results.ToList())); + /// + /// Implicitly converts an instance of + /// to an instance of . + /// + /// + /// This conversion is useful when you need to convert an operation result + /// to a strongly-typed result, for example, in contexts where the expected + /// result of a method is strongly-typed, but a child operation may return + /// a generic result, that is an error, and you want to propagate the error + /// without losing the type information. + /// + /// + /// The operation result to convert. + /// public static implicit operator OperationResult(OperationResult result) => new OperationResult(result.ResultType, default, result.Error); + /// + /// Implicitly converts an instance of to an + /// operation result that represents a success. + /// + /// + /// The value to convert to an operation result. + /// + /// public static implicit operator OperationResult(T value) => Success(value); + /// + /// Implicitly converts an instance of + /// to an operation result that represents a failure. + /// + /// + /// The error that caused the operation to fail. + /// + /// public static implicit operator OperationResult(OperationException error) => Fail(error); } } diff --git a/test/Deveel.Results.XUnit/OperationErrorTests.cs b/test/Deveel.Results.XUnit/OperationErrorTests.cs index 70a69fe..9e51b36 100644 --- a/test/Deveel.Results.XUnit/OperationErrorTests.cs +++ b/test/Deveel.Results.XUnit/OperationErrorTests.cs @@ -111,5 +111,27 @@ public static void ValidationError_WithResults() Assert.Equal("biz", error.Domain); Assert.Same(results, error.ValidationResults); } + + [Fact] + public static void OperationException_InnerExceptionIsOperationError() + { + var inner = new OperationException("err.2", "biz"); + var exception = new OperationException("err.1", "biz", "An error has occurred", inner); + + Assert.Equal("err.1", exception.ErrorCode); + Assert.Equal("biz", exception.ErrorDomain); + Assert.Equal("An error has occurred", exception.Message); + Assert.NotNull(exception.InnerException); + + var error = Assert.IsAssignableFrom(exception); + Assert.Equal("err.1", error.Code); + Assert.Equal("biz", error.Domain); + Assert.NotNull(error.InnerError); + + Assert.NotNull(exception.InnerException); + var innerError = Assert.IsAssignableFrom(exception.InnerException); + Assert.Equal("err.2", innerError.Code); + Assert.Equal("biz", innerError.Domain); + } } } diff --git a/test/Deveel.Results.XUnit/OperationResultOfTTests.cs b/test/Deveel.Results.XUnit/OperationResultOfTTests.cs index 253231b..79eaf1f 100644 --- a/test/Deveel.Results.XUnit/OperationResultOfTTests.cs +++ b/test/Deveel.Results.XUnit/OperationResultOfTTests.cs @@ -32,6 +32,24 @@ public static void OperationResultOfT_Success(Type resultType, object? value) Assert.Equal(value, GetValue(result)); } + [Fact] + public static void OperationResultOfT_NotChanged() + { + var result = OperationResult.NotChanged; + Assert.Equal(OperationResultType.Unchanged, result.ResultType); + Assert.Null(result.Error); + Assert.Equal(default, result.Value); + } + + [Fact] + public static void OperationResultOfT_Cancelled() + { + var result = OperationResult.Cancelled; + Assert.Equal(OperationResultType.Cancelled, result.ResultType); + Assert.Null(result.Error); + Assert.Equal(default, result.Value); + } + [Fact] public static void OperationResult_FailWithCode() { diff --git a/test/Deveel.Results.XUnit/OperationResultTests.cs b/test/Deveel.Results.XUnit/OperationResultTests.cs index b0a9545..8ed4c29 100644 --- a/test/Deveel.Results.XUnit/OperationResultTests.cs +++ b/test/Deveel.Results.XUnit/OperationResultTests.cs @@ -201,4 +201,19 @@ public static void OperationResult_ValidationErrors() Assert.True(result.HasValidationErrors()); Assert.Equal(errors, result.ValidationResults()); } + + [Fact] + public static void OperationResult_Fail_WithValidationErrors() + { + var errors = new List { + new ValidationResult("The value is required", new[] { "value" }), + new ValidationResult("The value must be greater than 0", new[] { "value" }) + }; + + var result = OperationResult.ValidationFailed("err.1", "biz", errors); + + Assert.True(result.IsError()); + Assert.True(result.HasValidationErrors()); + Assert.Equal(errors, result.ValidationResults()); + } }