Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/build error type response #12

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/ModelValidationAsyncActionFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Collections;
using System.Linq;
using System.Threading.Tasks;
using FluentValidation.Results;

namespace JSM.FluentValidation.AspNet.AsyncFilter
{
Expand Down Expand Up @@ -125,9 +126,14 @@ private async Task ValidateAsync(object value, ModelStateDictionary modelState)

var context = new ValidationContext<object>(value);
var result = await validator.ValidateAsync(context);
result.AddToModelState(modelState, string.Empty);
var errorCode = GetErrorCodeWithPrefixRuleType(result);

result.AddToModelState(modelState, errorCode);
}

private static string GetErrorCodeWithPrefixRuleType(ValidationResult result) =>
result.Errors.LastOrDefault(errorCode => errorCode.ErrorCode.Contains(RuleTypeConst.Prefix))?.ErrorCode;

private IValidator GetValidator(Type targetType)
{
var validatorType = typeof(IValidator<>).MakeGenericType(targetType);
Expand Down
34 changes: 34 additions & 0 deletions src/RuleTypeBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using System.Linq;

namespace JSM.FluentValidation.AspNet.AsyncFilter
{
/// <summary>
/// Extension to override error code with prefix and type.
/// </summary>
public static class RuleTypeBuilderExtensions
{
/// <summary>
/// Overrides the error code associated with this rule with prefix and type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProperty"></typeparam>
/// <param name="rule">The current rule</param>
/// <param name="type">The type used to override error code</param>
/// <returns></returns>
public static IRuleBuilderOptions<T, TProperty> WithRuleType<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, string type)
{
string propertyName = "";

var options = rule.Configure(x => propertyName = x.GetDisplayName(null));

if (!string.IsNullOrEmpty(propertyName))
options.WithName(propertyName);

options.WithErrorCode($"{RuleTypeConst.Prefix}.{type}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: we're gonna need to use the same extension method WithErrorCode in #8, but I think we can use it in both scenarios. We can set some predefined words as ErrorCodes to change the response status code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice @luizhlelis, make sense.
We can use a new Enum.ErrorsStatusCode in this lib, creating a new method like "WithStatusCode(Enum.ErrorsStatusCode)".


return options;
}
}
}
13 changes: 13 additions & 0 deletions src/RuleTypeConst.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace JSM.FluentValidation.AspNet.AsyncFilter
{
/// <summary>
/// Const used do configure method WithRuleType.
/// </summary>
public static class RuleTypeConst
{
/// <summary>
/// Prefix used to override property name.
/// </summary>
public const string Prefix = "_RuleType";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: maybe I'm missing something here, but is this prefix really required?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luizhlelis this prefix is important to set correctly type in response.
We only set type, if exists an internal ErrorCode with this prefix.

}
}
53 changes: 53 additions & 0 deletions tests/ModelValidationAsyncActionFilterWithRuleTypeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using FluentAssertions;
using JSM.FluentValidation.AspNet.AsyncFilter.Tests.Support;
using JSM.FluentValidation.AspNet.AsyncFilter.Tests.Support.Models;
using JSM.FluentValidation.AspNet.AsyncFilter.Tests.Support.Startups;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Xunit;

namespace JSM.FluentValidation.AspNet.AsyncFilter.Tests
{
public class ModelValidationAsyncActionFilterWithRuleTypeTests : WebAppFixture<StartupWithDefaultOptions>
{
private const string ControllerWithRuleTypeEndpoint = "WithRuleType";
private HttpClient Client { get; }

public ModelValidationAsyncActionFilterWithRuleTypeTests() => Client = CreateClient();

[Fact(DisplayName = "Should return OK when payload is valid")]
public async Task OnActionExecutionAsync_PayloadIsValid_ReturnOk()
{
// Arrange
var payload = new TestPayloadWithRuleType { Text = "Test" };

// Act
var response = await Client.PostAsJsonAsync($"{ControllerWithRuleTypeEndpoint}/test-validator", payload);

// Assert
response.Should().Be200Ok();
}

[Fact(DisplayName = "Should return bad request when payload is invalid")]
public async Task OnActionExecutionAsync_PayloadIsInvalid_ReturnBadRequest()
{
// Arrange
var payload = new TestPayloadWithRuleType { Text = "" };

// Act
var response = await Client.PostAsJsonAsync($"{ControllerWithRuleTypeEndpoint}/test-validator", payload);

// Assert
response.Should().Be400BadRequest();
var responseDetails = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>();
responseDetails.Title.Should().Be("One or more validation errors occurred.");
responseDetails.Errors.Should().BeEquivalentTo(new Dictionary<string, string[]>
{
{ $"{RuleTypeConst.Prefix}.SOME_RULE.Text", new[] { "Text can't be null" } }
});
}
}
}
13 changes: 13 additions & 0 deletions tests/Support/Controllers/WithRuleTypeController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using JSM.FluentValidation.AspNet.AsyncFilter.Tests.Support.Models;
using Microsoft.AspNetCore.Mvc;
using System;

namespace JSM.FluentValidation.AspNet.AsyncFilter.Tests.Support.Controllers
{
[ApiController, Route("[controller]")]
public class WithRuleTypeController : ControllerBase
{
[HttpPost("test-validator")]
public IActionResult Post([FromBody] TestPayloadWithRuleType request) => Ok();
}
}
20 changes: 20 additions & 0 deletions tests/Support/Models/TestPayloadWithRuleType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using FluentValidation;

namespace JSM.FluentValidation.AspNet.AsyncFilter.Tests.Support.Models
{
public class TestPayloadWithRuleType
{
public string Text { get; set; }
}

public class TestPayloadWithRuleTypeValidator : AbstractValidator<TestPayloadWithRuleType>
{
public TestPayloadWithRuleTypeValidator()
{
RuleFor(x => x.Text)
.NotEmpty()
.WithMessage("Text can't be null")
.WithRuleType("SOME_RULE");
}
}
}