Skip to content

Commit

Permalink
Update validation error names
Browse files Browse the repository at this point in the history
Update the validation error names to match attribute names
  • Loading branch information
smithgeek committed Oct 25, 2024
1 parent 008578f commit 0a91d69
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 222 deletions.
5 changes: 3 additions & 2 deletions Benchmarks/VoyagerBench/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public partial class Program { }
public partial class Request
{
[FromRoute(Name = "id")]
public int Id { get; init; }
public int UserId { get; init; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int Age { get; set; }
Expand All @@ -34,6 +34,7 @@ public static void Validate(AbstractValidator<Request> validator)
validator.RuleFor(x => x.LastName).NotEmpty().WithMessage("last needed");
validator.RuleFor(x => x.Age).GreaterThan(10).WithMessage("too young");
validator.RuleFor(x => x.PhoneNumbers).NotEmpty().WithMessage("phone needed");
validator.RuleFor(x => x.UserId).GreaterThan(5).WithMessage("id must be greater than 5");
}

}
Expand Down Expand Up @@ -75,7 +76,7 @@ public Response Post(Request req, ILogger<Program> logger)
{
return new Response()
{
Id = req.Id,
Id = req.UserId,
Name = req.FirstName + " " + req.LastName,
Age = req.Age,
PhoneNumber = req.PhoneNumbers?.FirstOrDefault()
Expand Down
219 changes: 1 addition & 218 deletions Benchmarks/VoyagerBench/TestingGrounds.cs
Original file line number Diff line number Diff line change
@@ -1,218 +1 @@
#nullable enable
using FluentValidation;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.Extensions.Options;
using System.Text.Json;
using Voyager;
using Voyager.ModelBinding;

namespace Voyager.Generated.VoyagerBench_VoyagerSourceGen
{
internal class EndpointMapper : Voyager.IVoyagerMapping
{
public void MapEndpoints(WebApplication app)
{
var modelBinder = app.Services.GetService<IModelBinder>() ?? new ModelBinder();
var jsonOptions = app.Services.GetRequiredService<IOptions<JsonOptions>>().Value.SerializerOptions;
var stringProvider = app.Services.GetService<Voyager.ModelBinding.IStringValuesProvider>() ?? new Voyager.ModelBinding.StringValuesProvider();
var inst_VoyagerApi_NoRequestEndpoint = app.Services.GetRequiredService<VoyagerApi.NoRequestEndpoint>();
var inst_VoyagerApi_StaticEndpoint = app.Services.GetRequiredService<VoyagerApi.StaticEndpoint>();
var inst_VoyagerApi_Endpoint = app.Services.GetRequiredService<VoyagerApi.Endpoint>();
var instVoyagerApi_EndpointPostValidator = new VoyagerApi_EndpointPostValidator();
var inst_VoyagerApi_AnonymousEndpoint = app.Services.GetRequiredService<VoyagerApi.AnonymousEndpoint>();
var inst_VoyagerApi_Duplicate_AnonymousEndpoint = app.Services.GetRequiredService<VoyagerApi.Duplicate.AnonymousEndpoint>();
var inst_VoyagerApi_MultipleInjections = app.Services.GetRequiredService<VoyagerApi.MultipleInjections>();
app.MapGet("/norequest", (Microsoft.AspNetCore.Http.HttpContext context) =>
{
return Microsoft.AspNetCore.Http.TypedResults.Ok(inst_VoyagerApi_NoRequestEndpoint.Get());
}
).WithMetadata(new Func<Voyager.OpenApi.VoyagerOpenApiMetadata>(() =>
{
var builder = Voyager.OpenApi.OperationBuilderFactory.Create(app.Services, new());
builder.AddResponse(400, typeof(Microsoft.AspNetCore.Http.HttpValidationProblemDetails));
builder.AddResponse(200, typeof(int));
return new Voyager.OpenApi.VoyagerOpenApiMetadata { Operation = builder.Build() };
}
)());
app.MapGet("/static", (Microsoft.AspNetCore.Http.HttpContext context) =>
{
return (Microsoft.AspNetCore.Http.IResult)(VoyagerApi.StaticEndpoint.Get(context.RequestServices.GetRequiredService<VoyagerApi.Service>()));
}
).WithMetadata(new Func<Voyager.OpenApi.VoyagerOpenApiMetadata>(() =>
{
var builder = Voyager.OpenApi.OperationBuilderFactory.Create(app.Services, new());
builder.AddResponse(400, typeof(Microsoft.AspNetCore.Http.HttpValidationProblemDetails));
builder.AddResponse(Microsoft.AspNetCore.Http.TypedResults.Ok().StatusCode, typeof(VoyagerApi_StaticEndpointGetResponse0));
return new Voyager.OpenApi.VoyagerOpenApiMetadata { Operation = builder.Build() };
}
)());
VoyagerApi.Endpoint.Configure(app.MapPost("/benchmark/ok/{id}", async (Microsoft.AspNetCore.Http.HttpContext context, [Microsoft.AspNetCore.Mvc.FromRouteAttribute(Name = "id")] int id) =>

{
var body = await JsonSerializer.DeserializeAsync<VoyagerApi_EndpointPostRequest>(context.Request.Body, jsonOptions);
var request = new VoyagerApi.Request
{
Id = id,
FirstName = body?.FirstName ?? null,
LastName = body?.LastName ?? null,
Age = body?.Age ?? default!,
PhoneNumbers = body?.PhoneNumbers ?? null,
};
var validationResult = await instVoyagerApi_EndpointPostValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
return Microsoft.AspNetCore.Http.Results.ValidationProblem(validationResult.ToDictionary());
}
return Microsoft.AspNetCore.Http.TypedResults.Ok(inst_VoyagerApi_Endpoint.Post(request, context.RequestServices.GetRequiredService<Microsoft.Extensions.Logging.ILogger<VoyagerApi.Program>>()));
}
).WithMetadata(new Func<Voyager.OpenApi.VoyagerOpenApiMetadata>(() =>
{
var builder = Voyager.OpenApi.OperationBuilderFactory.Create(app.Services, new());
builder.AddParameter("id", Microsoft.OpenApi.Models.ParameterLocation.Path, typeof(int), false);
builder.AddBody(typeof(VoyagerApi_EndpointPostRequest));
builder.AddResponse(400, typeof(Microsoft.AspNetCore.Http.HttpValidationProblemDetails));
builder.AddResponse(200, typeof(VoyagerApi.Response));
return new Voyager.OpenApi.VoyagerOpenApiMetadata { Operation = builder.Build() };
}
)()));
app.MapGet("/anonymous", async (Microsoft.AspNetCore.Http.HttpContext context) =>
{
var body = await JsonSerializer.DeserializeAsync<VoyagerApi_AnonymousEndpointGetRequest>(context.Request.Body, jsonOptions);
var request = new VoyagerApi.AnonymousEndpoint.Body
{
Test = body?.Test ?? null,
};
return (Microsoft.AspNetCore.Http.IResult)(inst_VoyagerApi_AnonymousEndpoint.Get(request));
}
).WithMetadata(new Func<Voyager.OpenApi.VoyagerOpenApiMetadata>(() =>
{
var builder = Voyager.OpenApi.OperationBuilderFactory.Create(app.Services, new());
builder.AddBody(typeof(VoyagerApi_AnonymousEndpointGetRequest));
builder.AddResponse(400, typeof(Microsoft.AspNetCore.Http.HttpValidationProblemDetails));
builder.AddResponse(Microsoft.AspNetCore.Http.TypedResults.Ok().StatusCode, typeof(VoyagerApi_AnonymousEndpointGetResponse0));
builder.AddResponse(Microsoft.AspNetCore.Http.TypedResults.Ok().StatusCode, typeof(VoyagerApi_AnonymousEndpointGetResponse1));
return new Voyager.OpenApi.VoyagerOpenApiMetadata { Operation = builder.Build() };
}
)());
app.MapGet("/duplicate/anonymous", async (Microsoft.AspNetCore.Http.HttpContext context) =>
{
var body = await JsonSerializer.DeserializeAsync<VoyagerApi_Duplicate_AnonymousEndpointGetRequest>(context.Request.Body, jsonOptions);
var request = new VoyagerApi.Duplicate.AnonymousEndpoint.Body
{
Test = body?.Test ?? null,
Value = body?.Value ?? default!,
};
return (Microsoft.AspNetCore.Http.IResult)(inst_VoyagerApi_Duplicate_AnonymousEndpoint.Get(request));
}
).WithMetadata(new Func<Voyager.OpenApi.VoyagerOpenApiMetadata>(() =>
{
var builder = Voyager.OpenApi.OperationBuilderFactory.Create(app.Services, new());
builder.AddBody(typeof(VoyagerApi_Duplicate_AnonymousEndpointGetRequest));
builder.AddResponse(400, typeof(Microsoft.AspNetCore.Http.HttpValidationProblemDetails));
builder.AddResponse(Microsoft.AspNetCore.Http.TypedResults.Ok().StatusCode, typeof(VoyagerApi_Duplicate_AnonymousEndpointGetResponse0));
builder.AddResponse(Microsoft.AspNetCore.Http.TypedResults.Ok().StatusCode, typeof(VoyagerApi_Duplicate_AnonymousEndpointGetResponse1));
return new Voyager.OpenApi.VoyagerOpenApiMetadata { Operation = builder.Build() };
}
)());
app.MapGet("/multipleInjections", (Microsoft.AspNetCore.Http.HttpContext context) =>
{
return (Microsoft.AspNetCore.Http.IResult)(inst_VoyagerApi_MultipleInjections.Get(context.RequestServices.GetRequiredService<VoyagerApi.Service>()));
}
).WithMetadata(new Func<Voyager.OpenApi.VoyagerOpenApiMetadata>(() =>
{
var builder = Voyager.OpenApi.OperationBuilderFactory.Create(app.Services, new());
builder.AddResponse(400, typeof(Microsoft.AspNetCore.Http.HttpValidationProblemDetails));
builder.AddResponse(Microsoft.AspNetCore.Http.TypedResults.Ok().StatusCode, null);
return new Voyager.OpenApi.VoyagerOpenApiMetadata { Operation = builder.Build() };
}
)());
app.MapPost("/multipleInjections", (Microsoft.AspNetCore.Http.HttpContext context) =>
{
return (Microsoft.AspNetCore.Http.IResult)(inst_VoyagerApi_MultipleInjections.Post(context.RequestServices.GetRequiredService<VoyagerApi.Service>()));
}
).WithMetadata(new Func<Voyager.OpenApi.VoyagerOpenApiMetadata>(() =>
{
var builder = Voyager.OpenApi.OperationBuilderFactory.Create(app.Services, new());
builder.AddResponse(400, typeof(Microsoft.AspNetCore.Http.HttpValidationProblemDetails));
builder.AddResponse(Microsoft.AspNetCore.Http.TypedResults.Ok().StatusCode, null);
return new Voyager.OpenApi.VoyagerOpenApiMetadata { Operation = builder.Build() };
}
)());
}
#pragma warning disable CS8618
private class VoyagerApi_StaticEndpointGetResponse0
{
public bool test { get; set; }
}
#pragma warning restore CS8618
#pragma warning disable CS8618
private class VoyagerApi_EndpointPostRequest
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int Age { get; set; }
public System.Collections.Generic.IEnumerable<string>? PhoneNumbers { get; set; }
}
#pragma warning restore CS8618
public class VoyagerApi_EndpointPostValidator : AbstractValidator<VoyagerApi.Request>
{
public VoyagerApi_EndpointPostValidator()
{
VoyagerApi.Request.Validate(this);
}
}
#pragma warning disable CS8618
private class VoyagerApi_AnonymousEndpointGetRequest
{
public string? Test { get; set; }
}
#pragma warning restore CS8618
#pragma warning disable CS8618
private class VoyagerApi_AnonymousEndpointGetResponse0
{
public string something { get; set; }
}
#pragma warning restore CS8618
#pragma warning disable CS8618
private class VoyagerApi_AnonymousEndpointGetResponse1
{
public string? result { get; set; }
}
#pragma warning restore CS8618
#pragma warning disable CS8618
private class VoyagerApi_Duplicate_AnonymousEndpointGetRequest
{
public string? Test { get; set; }
public int Value { get; set; }
}
#pragma warning restore CS8618
#pragma warning disable CS8618
private class VoyagerApi_Duplicate_AnonymousEndpointGetResponse0
{
public string something { get; set; }
}
#pragma warning restore CS8618
#pragma warning disable CS8618
private class VoyagerApi_Duplicate_AnonymousEndpointGetResponse1
{
public string? result { get; set; }
}
#pragma warning restore CS8618
}
}

namespace Microsoft.Extensions.DependencyInjection
{
internal static class VoyagerEndpoints
{
internal static void AddVoyager(this IServiceCollection services)
{
services.AddTransient<VoyagerApi.NoRequestEndpoint>();
services.AddTransient<VoyagerApi.Endpoint>();
services.AddTransient<VoyagerApi.AnonymousEndpoint>();
services.AddTransient<VoyagerApi.Duplicate.AnonymousEndpoint>();
services.AddTransient<VoyagerApi.MultipleInjections>();
services.AddTransient<IVoyagerMapping, Voyager.Generated.VoyagerBench_VoyagerSourceGen.EndpointMapper>();
}
}
}

1 change: 1 addition & 0 deletions Tests/UnitTests/UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Benchmarks\VoyagerBench\VoyagerBench.csproj" />
<ProjectReference Include="..\..\src\Voyager\Voyager.csproj" />
</ItemGroup>

Expand Down
37 changes: 37 additions & 0 deletions Tests/UnitTests/ValidatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using FluentValidation;

namespace UnitTests
{
public class ValidatorTests
{
[Fact]
public async Task ValidateName()
{
var validator = new VoyagerApi_EndpointPostValidator();
var request = new VoyagerApi.Request();
var result = await validator.ValidateAsync(request);
var dict = result.ToDictionary();
dict.ReplaceKey("UserId", "id");
}

private class VoyagerApi_EndpointPostValidator : AbstractValidator<VoyagerApi.Request>
{
public VoyagerApi_EndpointPostValidator()
{
VoyagerApi.Request.Validate(this);
}
}
}

public static class DictionaryExt
{
public static void ReplaceKey(this IDictionary<string, string[]> dictionary, string oldKey, string newKey)
{
if (dictionary.TryGetValue(oldKey, out var value))
{
dictionary.Remove(oldKey);
dictionary.TryAdd(newKey, value);
}
}
}
}
2 changes: 1 addition & 1 deletion Voyager.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcControllers", "Benchmark
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VoyagerBench", "Benchmarks\VoyagerBench\VoyagerBench.csproj", "{9C2A4518-8D6C-447D-9688-1A1CBF72BE74}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "Tests\UnitTests\UnitTests.csproj", "{6795795B-7716-47DF-89B9-66088A524926}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "Tests\UnitTests\UnitTests.csproj", "{6795795B-7716-47DF-89B9-66088A524926}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
22 changes: 21 additions & 1 deletion src/Voyager.SourceGenerator/VoyagerSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ private string EndpointMapping(GeneratorExecutionContext context)
.AddBase("Voyager.IVoyagerMapping")
.AddMethod(new("MapEndpoints", access: Access.Public))
.AddParameter("WebApplication app");
endpointMapper.AddMethod(new("ReplaceKey", access: Access.Private, isStatic: true))
.AddParameter("IDictionary<string, string[]> validationErrors")
.AddParameter("string oldKey")
.AddParameter("string newKey")
.AddIf("validationErrors.TryGetValue(oldKey, out var value)")
.AddStatement("validationErrors.Remove(oldKey);")
.AddStatement("validationErrors.TryAdd(newKey, value);");
var endpointsInitRegion = mapEndpoints.AddRegion();
endpointsInitRegion.AddStatement("var jsonOptions = app.Services.GetRequiredService<IOptions<JsonOptions>>().Value.SerializerOptions;");
var modelBinderAdded = false;
Expand Down Expand Up @@ -231,7 +238,20 @@ private string EndpointMapping(GeneratorExecutionContext context)
if (!parameters.Contains("validationResult"))
{
var @if = mapContent.AddIf("!validationResult.IsValid");
@if.AddStatement("return Microsoft.AspNetCore.Http.Results.ValidationProblem(validationResult.ToDictionary());");
var propertiesWithAttributes = request.Properties.Where(p => p.Attribute != null);
if (propertiesWithAttributes.Any())
{
@if.AddStatement("var dictionary = validationResult.ToDictionary();");
foreach (var property in propertiesWithAttributes)
{
@if.AddStatement($"ReplaceKey(dictionary, \"{property.Property.Name}\", \"{property.SourceName}\");");
}
@if.AddStatement("return Microsoft.AspNetCore.Http.Results.ValidationProblem(dictionary);");
}
else
{
@if.AddStatement("return Microsoft.AspNetCore.Http.Results.ValidationProblem(validationResult.ToDictionary());");
}
}
}
var typedReturn = endpoint.IsIResult ? "(Microsoft.AspNetCore.Http.IResult)" : "Microsoft.AspNetCore.Http.TypedResults.Ok";
Expand Down

0 comments on commit 0a91d69

Please sign in to comment.