Skip to content

Commit

Permalink
Merge pull request #53 from MrDave1999/refactor/issue-52
Browse files Browse the repository at this point in the history
improve: Change error message format when validation fails from Fluent Validation
  • Loading branch information
MrDave1999 authored Dec 15, 2023
2 parents 9576122 + 47d677f commit 23f5fdb
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 20 deletions.
9 changes: 9 additions & 0 deletions src/Core/Resources/ResponseMessages.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Core/Resources/ResponseMessages.es.resx
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@
<data name="ObtainedResources" xml:space="preserve">
<value>Recursos obtenidos con éxito</value>
</data>
<data name="PropertyFailedValidation" xml:space="preserve">
<value>Propiedad '{0}' falló en la validación. El error fue: {1}</value>
</data>
<data name="Success" xml:space="preserve">
<value>Operación ejecutada con éxito</value>
</data>
Expand Down
3 changes: 3 additions & 0 deletions src/Core/Resources/ResponseMessages.resx
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@
<data name="ObtainedResources" xml:space="preserve">
<value>Resources successfully obtained</value>
</data>
<data name="PropertyFailedValidation" xml:space="preserve">
<value>'{0}' property failed validation. Error was: {1}</value>
</data>
<data name="Success" xml:space="preserve">
<value>Operation successfully executed</value>
</data>
Expand Down
1 change: 1 addition & 0 deletions src/Core/SimpleResults.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<ItemGroup>
<InternalsVisibleTo Include="SimpleResults.Tests" />
<InternalsVisibleTo Include="SimpleResults.AspNetCore" />
<InternalsVisibleTo Include="SimpleResults.FluentValidation" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 9 additions & 2 deletions src/FluentValidation/SRFluentValidationResultExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using FluentValidation.Results;
using SimpleResults.Resources;

namespace SimpleResults;

Expand All @@ -15,7 +16,7 @@ public static class SRFluentValidationResultExtensions
/// <returns>
/// <c>true</c> if the validation result is failed; otherwise <c>false</c>.
/// </returns>
public static bool IsFailed(this ValidationResult result)
public static bool IsFailed(this ValidationResult result)
=> !result.IsValid;

/// <summary>
Expand All @@ -24,7 +25,13 @@ public static bool IsFailed(this ValidationResult result)
/// <param name="result">The result of running a validator.</param>
/// <returns>A collection that contains error messages.</returns>
public static IEnumerable<string> AsErrors(this ValidationResult result)
=> result.Errors.Select(failure => failure.ErrorMessage);
=> result.Errors.Select(GetErrorMessage);

private static string GetErrorMessage(ValidationFailure failure)
=> string.Format(
ResponseMessages.PropertyFailedValidation,
failure.PropertyName,
failure.ErrorMessage);

/// <summary>
/// Represents a validation error that prevents the underlying service from completing.
Expand Down
69 changes: 69 additions & 0 deletions tests/FluentValidation/FailedValidationTestCases.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace SimpleResults.Tests.FluentValidation;

public class FailedValidationTestCases : IEnumerable
{
public IEnumerator GetEnumerator()
{
yield return new object[]
{
new Order
{
Customer = string.Empty,
Description = string.Empty,
DeliveryAddress = default,
Details = default
},
new[]
{
"'Customer' property failed validation. Error was: 'Customer' must not be empty.",
"'Description' property failed validation. Error was: 'Description' must not be empty.",
"'DeliveryAddress' property failed validation. Error was: 'Delivery Address' must not be empty.",
"'Details' property failed validation. Error was: 'Details' must not be empty."
}
};

yield return new object[]
{
new Order
{
Customer = string.Empty,
Description = string.Empty,
DeliveryAddress = new Address
{
Description = string.Empty,
Postcode = string.Empty,
Country = string.Empty
},
Details = new List<OrderDetail>
{
new()
{
Product = string.Empty,
Price = 0,
Amount = 0
},
new()
{
Product = string.Empty,
Price = -1,
Amount = -1
}
}
},
new[]
{
"'Customer' property failed validation. Error was: 'Customer' must not be empty.",
"'Description' property failed validation. Error was: 'Description' must not be empty.",
"'DeliveryAddress.Description' property failed validation. Error was: 'Description' must not be empty.",
"'DeliveryAddress.Postcode' property failed validation. Error was: 'Postcode' must not be empty.",
"'DeliveryAddress.Country' property failed validation. Error was: 'Country' must not be empty.",
"'Details[0].Product' property failed validation. Error was: 'Product' must not be empty.",
"'Details[0].Price' property failed validation. Error was: 'Price' must be greater than '0'.",
"'Details[0].Amount' property failed validation. Error was: 'Amount' must be greater than '0'.",
"'Details[1].Product' property failed validation. Error was: 'Product' must not be empty.",
"'Details[1].Price' property failed validation. Error was: 'Price' must be greater than '0'.",
"'Details[1].Amount' property failed validation. Error was: 'Amount' must be greater than '0'."
}
};
}
}
41 changes: 26 additions & 15 deletions tests/FluentValidation/ValidationResultExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,43 @@ public void IsFailed_WhenValidationResultIsSuccess_ShouldReturnsFalse()
actual.Should().BeFalse();
}

[Test]
public void AsErrors_WhenValidationResultIsReceived_ShouldReturnsCollectionOfErrorMessages()
[TestCaseSource(typeof(FailedValidationTestCases))]
public void AsErrors_WhenValidationResultIsReceived_ShouldReturnsCollectionOfErrorMessages(
Order order,
string[] expectedErrors)
{
// Arrange
var person = new Person { Name = string.Empty };
var validator = new PersonValidator();
ValidationResult result = validator.Validate(person);
var expectedCollection = new[]
{
"'Name' must not be empty."
};
var validator = new OrderValidator();
ValidationResult result = validator.Validate(order);

// Act
IEnumerable<string> actual = result.AsErrors();

// Assert
actual.Should().BeEquivalentTo(expectedCollection);
actual.Should().BeEquivalentTo(expectedErrors);
}

[Test]
public void AsErrors_WhenThereAreNoErrors_ShouldReturnsEmptyCollection()
{
// Arrange
var person = new Person { Name = "Alice" };
var validator = new PersonValidator();
ValidationResult result = validator.Validate(person);
var order = new Order
{
Customer = "Bob",
Description = "Test",
DeliveryAddress = new Address { Description = "D", Postcode = "P", Country = "C" },
Details = new List<OrderDetail>
{
new()
{
Product = "P",
Price = 5000,
Amount = 2
}
}
};
var validator = new OrderValidator();
ValidationResult result = validator.Validate(order);

// Act
IEnumerable<string> actual = result.AsErrors();
Expand All @@ -76,7 +87,7 @@ public void Invalid_WhenResultIsInvalidWithoutMessage_ShouldReturnsResultObject(
var expectedMessage = ResponseMessages.ValidationErrors;
var expectedErrors = new[]
{
"'Name' must not be empty."
"'Name' property failed validation. Error was: 'Name' must not be empty."
};

// Act
Expand All @@ -100,7 +111,7 @@ public void Invalid_WhenResultIsInvalidWithMessage_ShouldReturnsResultObject()
var expectedMessage = "Error";
var expectedErrors = new[]
{
"'Name' must not be empty."
"'Name' property failed validation. Error was: 'Name' must not be empty."
};

// Act
Expand Down
18 changes: 18 additions & 0 deletions tests/FluentValidation/Validators/DeliveryAddressValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace SimpleResults.Tests.FluentValidation.Validators;

public class Address
{
public string Description { get; init; }
public string Country { get; init; }
public string Postcode { get; init; }
}

public class DeliveryAddressValidator : AbstractValidator<Address>
{
public DeliveryAddressValidator()
{
RuleFor(d => d.Description).NotEmpty();
RuleFor(d => d.Country).NotEmpty();
RuleFor(d => d.Postcode).NotEmpty();
}
}
18 changes: 18 additions & 0 deletions tests/FluentValidation/Validators/OrderDetailValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace SimpleResults.Tests.FluentValidation.Validators;

public class OrderDetail
{
public string Product { get; init; }
public int Amount { get; init; }
public double Price { get; init; }
}

public class OrderDetailValidator : AbstractValidator<OrderDetail>
{
public OrderDetailValidator()
{
RuleFor(o => o.Product).NotEmpty();
RuleFor(o => o.Amount).GreaterThan(0);
RuleFor(o => o.Price).GreaterThan(0);
}
}
23 changes: 23 additions & 0 deletions tests/FluentValidation/Validators/OrderValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace SimpleResults.Tests.FluentValidation.Validators;

public class Order
{
public string Customer { get; init; }
public string Description { get; init; }
public Address DeliveryAddress { get; init; }
public IEnumerable<OrderDetail> Details { get; init; }
}

public class OrderValidator : AbstractValidator<Order>
{
public OrderValidator()
{
RuleFor(o => o.Customer).NotEmpty();
RuleFor(o => o.Description).NotEmpty();
RuleFor(o => o.DeliveryAddress)
.NotEmpty()
.SetValidator(new DeliveryAddressValidator());
RuleFor(o => o.Details).NotEmpty();
RuleForEach(o => o.Details).SetValidator(new OrderDetailValidator());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace SimpleResults.Tests.FluentValidation;
namespace SimpleResults.Tests.FluentValidation.Validators;

public class PersonValidator : AbstractValidator<Person>
{
Expand Down
4 changes: 3 additions & 1 deletion tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
global using System.Globalization;
global using System.Text.Json;
global using System.Collections;
global using NUnit.Framework;
global using FluentAssertions;
global using FluentValidation;
Expand All @@ -8,4 +9,5 @@
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Http.HttpResults;
global using Microsoft.AspNetCore.Mvc.Filters;
global using SimpleResults.Resources;
global using SimpleResults.Resources;
global using SimpleResults.Tests.FluentValidation.Validators;
2 changes: 1 addition & 1 deletion tests/Utils/TestFixtureProjectSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ public class TestFixtureProjectSetup
public void RunBeforeAllTestFixtures()
{
// Allows to load the default resource in English.
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en");
}
}

0 comments on commit 23f5fdb

Please sign in to comment.