From 31464ea9c649a323c88a073eab23774fbe03c8ce Mon Sep 17 00:00:00 2001 From: Iliyan Angelov <51085480+IliyanAng@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:32:36 +0200 Subject: [PATCH 1/2] Add support for single validation error with Result.Invalid (#144) * Add support for single validation error with Result.Invalid - Support for single validation error in the Result.Invalid method - Added unit tests to test the behavior * Update src/Ardalis.Result/Result.Void.cs * Update src/Ardalis.Result/Result.cs --------- Co-authored-by: Steve Smith --- src/Ardalis.Result/Result.Void.cs | 10 ++++++++++ src/Ardalis.Result/Result.cs | 10 ++++++++++ .../ResultConstructor.cs | 10 +++++++++- .../ResultVoidConstructor.cs | 20 +++++++++++++++++-- .../Ardalis.Result.UnitTests/ResultVoidMap.cs | 14 ++++++++++++- .../ResultVoidToResultOfT.cs | 20 ++++++++++++++++++- 6 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/Ardalis.Result/Result.Void.cs b/src/Ardalis.Result/Result.Void.cs index 7b0ee94..7c69e74 100644 --- a/src/Ardalis.Result/Result.Void.cs +++ b/src/Ardalis.Result/Result.Void.cs @@ -77,6 +77,16 @@ public static Result ErrorWithCorrelationId(string correlationId, params string[ }; } + /// + /// Represents the validation error that prevents the underlying service from completing. + /// + /// The validation error encountered + /// A Result + public new static Result Invalid(ValidationError validationError) + { + return new Result(ResultStatus.Invalid) { ValidationErrors = { validationError } }; + } + /// /// Represents validation errors that prevent the underlying service from completing. /// diff --git a/src/Ardalis.Result/Result.cs b/src/Ardalis.Result/Result.cs index 5bce85d..845c02e 100644 --- a/src/Ardalis.Result/Result.cs +++ b/src/Ardalis.Result/Result.cs @@ -111,6 +111,16 @@ public static Result Error(params string[] errorMessages) return new Result(ResultStatus.Error) { Errors = errorMessages }; } + /// + /// Represents a validation error that prevents the underlying service from completing. + /// + /// The validation error encountered + /// A Result + public static Result Invalid(ValidationError validationError) + { + return new Result(ResultStatus.Invalid) { ValidationErrors = { validationError } }; + } + /// /// Represents validation errors that prevent the underlying service from completing. /// diff --git a/tests/Ardalis.Result.UnitTests/ResultConstructor.cs b/tests/Ardalis.Result.UnitTests/ResultConstructor.cs index 639f416..029e6d2 100644 --- a/tests/Ardalis.Result.UnitTests/ResultConstructor.cs +++ b/tests/Ardalis.Result.UnitTests/ResultConstructor.cs @@ -207,10 +207,18 @@ public void InitializedIsSuccessFalseForForbiddenFactoryCall() Assert.False(result.IsSuccess); } + [Fact] + public void InitializedIsSuccessFalseForInvalidListFactoryCall() + { + var result = Result.Invalid(new List()); + + Assert.False(result.IsSuccess); + } + [Fact] public void InitializedIsSuccessFalseForInvalidFactoryCall() { - var result = Result.Invalid(null); + var result = Result.Invalid(new ValidationError()); Assert.False(result.IsSuccess); } diff --git a/tests/Ardalis.Result.UnitTests/ResultVoidConstructor.cs b/tests/Ardalis.Result.UnitTests/ResultVoidConstructor.cs index 48b4875..0dcc839 100644 --- a/tests/Ardalis.Result.UnitTests/ResultVoidConstructor.cs +++ b/tests/Ardalis.Result.UnitTests/ResultVoidConstructor.cs @@ -1,5 +1,4 @@ using FluentAssertions; -using System; using System.Collections.Generic; using System.Linq; using Xunit; @@ -73,7 +72,7 @@ public void InitializesErrorResultWithCorrelationIdWithFactoryMethod() } [Fact] - public void InitializesInvalidResultWithFactoryMethod() + public void InitializesInvalidResultWithMultipleValidationErrorsWithFactoryMethod() { var validationErrors = new List { @@ -98,6 +97,23 @@ public void InitializesInvalidResultWithFactoryMethod() result.ValidationErrors.Should().ContainEquivalentOf(new ValidationError { ErrorMessage = "PostalCode cannot exceed 10 characters", Identifier = "postalCode" }); } + [Fact] + public void InitializesInvalidResultWithSingleValidationErrorWithFactoryMethod() + { + var validationError = new ValidationError + { + Identifier = "name", + ErrorMessage = "Name is required" + }; + + var result = Result.Invalid(validationError); + + Assert.Null(result.Value); + Assert.Equal(ResultStatus.Invalid, result.Status); + + result.ValidationErrors.Should().ContainEquivalentOf(new ValidationError { ErrorMessage = "Name is required", Identifier = "name" }); + } + [Fact] public void InitializesNotFoundResultWithFactoryMethod() { diff --git a/tests/Ardalis.Result.UnitTests/ResultVoidMap.cs b/tests/Ardalis.Result.UnitTests/ResultVoidMap.cs index d62999d..aaaf8b7 100644 --- a/tests/Ardalis.Result.UnitTests/ResultVoidMap.cs +++ b/tests/Ardalis.Result.UnitTests/ResultVoidMap.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using System.Collections.Generic; using Xunit; namespace Ardalis.Result.UnitTests @@ -48,10 +49,21 @@ public void ShouldProduceForbidden() actual.Value.Should().BeNull(); } + [Fact] + public void ShouldProduceInvalidWithEmptyList() + { + var result = Result.Invalid(new List()); + + var actual = result.Map(_ => "This should be ignored"); + + actual.Status.Should().Be(ResultStatus.Invalid); + actual.Value.Should().BeNull(); + } + [Fact] public void ShouldProduceInvalid() { - var result = Result.Invalid(new()); + var result = Result.Invalid(new ValidationError()); var actual = result.Map(_ => "This should be ignored"); diff --git a/tests/Ardalis.Result.UnitTests/ResultVoidToResultOfT.cs b/tests/Ardalis.Result.UnitTests/ResultVoidToResultOfT.cs index 593939e..f596bdf 100644 --- a/tests/Ardalis.Result.UnitTests/ResultVoidToResultOfT.cs +++ b/tests/Ardalis.Result.UnitTests/ResultVoidToResultOfT.cs @@ -23,7 +23,7 @@ public void ConvertFromErrorResultOfUnit(params string[] errors) } [Fact] - public void ConvertFromInvalidResultOfUnit() + public void ConvertFromInvalidResultOfUnitWithValidationErrorList() { var validationErrors = new List { @@ -49,6 +49,24 @@ public void ConvertFromInvalidResultOfUnit() result.ValidationErrors.Should().ContainEquivalentOf(new ValidationError { ErrorMessage = "PostalCode cannot exceed 10 characters", Identifier = "postalCode" }); } + [Fact] + public void ConvertFromInvalidResultOfUnitWithValidationError() + { + var validationError = new ValidationError + { + Identifier = "name", + ErrorMessage = "Name is required" + }; + + var result = DoBusinessOperationExample(Result.Invalid(validationError)); + + Assert.Null(result.Value); + Assert.Equal(ResultStatus.Invalid, result.Status); + + result.Status.Should().Be(ResultStatus.Invalid); + result.ValidationErrors.Should().ContainEquivalentOf(new ValidationError { ErrorMessage = "Name is required", Identifier = "name" }); + } + [Fact] public void ConvertFromNotFoundResultOfUnit() { From 8e93188e57b7a52311650ec2a941cf5f223d889c Mon Sep 17 00:00:00 2001 From: Enoch Mtz R Date: Tue, 12 Sep 2023 17:17:01 -0400 Subject: [PATCH 2/2] Added CriticalError to ResultStatuses (#140) (#141) --- .../ResultStatusMap.cs | 20 +++++++++++++++++-- src/Ardalis.Result/Result.cs | 12 +++++++++++ src/Ardalis.Result/ResultExtensions.cs | 1 + src/Ardalis.Result/ResultStatus.cs | 1 + 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Ardalis.Result.AspNetCore/ResultStatusMap.cs b/src/Ardalis.Result.AspNetCore/ResultStatusMap.cs index 9cb29cd..d86e1ae 100644 --- a/src/Ardalis.Result.AspNetCore/ResultStatusMap.cs +++ b/src/Ardalis.Result.AspNetCore/ResultStatusMap.cs @@ -35,7 +35,10 @@ public ResultStatusMap AddDefaultMap() .For(ResultStatus.NotFound, HttpStatusCode.NotFound, resultStatusOptions => resultStatusOptions .With(NotFoundEntity)) .For(ResultStatus.Conflict, HttpStatusCode.Conflict, resultStatusOptions => resultStatusOptions - .With(ConflictEntity)); + .With(ConflictEntity)) + .For(ResultStatus.CriticalError, HttpStatusCode.InternalServerError, resultStatusOptions => + resultStatusOptions + .With(CriticalEntity)); } /// @@ -139,6 +142,19 @@ private static ProblemDetails ConflictEntity(ControllerBase controller, IResult Detail = result.Errors.Any() ? details.ToString() : null }; } + + private static ProblemDetails CriticalEntity(ControllerBase controller, IResult result) + { + var details = new StringBuilder("Next error(s) occured:"); + + foreach (var error in result.Errors) details.Append("* ").Append(error).AppendLine(); + + return new ProblemDetails + { + Title = "Something went wrong.", + Detail = result.Errors.Any() ? details.ToString() : null + }; + } } public class ResultStatusOptions @@ -217,4 +233,4 @@ public ResultStatusOptions With(Type responseType, Func Conflict(params string[] errorMessages) { return new Result(ResultStatus.Conflict) { Errors = errorMessages }; } + + /// + /// Represents a critical error that occurred during the execution of the service. + /// Everything provided by the user was valid, but the service was unable to complete due to an exception. + /// See also HTTP 500 Internal Server Error: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors + /// + /// A list of string error messages. + /// A Result + public static Result CriticalError(params string[] errorMessages) + { + return new Result(ResultStatus.CriticalError) { Errors = errorMessages }; + } } } diff --git a/src/Ardalis.Result/ResultExtensions.cs b/src/Ardalis.Result/ResultExtensions.cs index 4a4bba1..4643ce1 100644 --- a/src/Ardalis.Result/ResultExtensions.cs +++ b/src/Ardalis.Result/ResultExtensions.cs @@ -29,6 +29,7 @@ public static Result Map(this Result.Conflict(result.Errors.ToArray()) : Result.Conflict(); + case ResultStatus.CriticalError: return Result.CriticalError(result.Errors.ToArray()); default: throw new NotSupportedException($"Result {result.Status} conversion is not supported."); } diff --git a/src/Ardalis.Result/ResultStatus.cs b/src/Ardalis.Result/ResultStatus.cs index de2e3fc..57e2750 100644 --- a/src/Ardalis.Result/ResultStatus.cs +++ b/src/Ardalis.Result/ResultStatus.cs @@ -9,5 +9,6 @@ public enum ResultStatus Invalid, NotFound, Conflict, + CriticalError } }