Skip to content

Commit c5d6996

Browse files
fix: allow same type names in different namespaces in validator generator (#62050)
1 parent b7aa14c commit c5d6996

12 files changed

+807
-640
lines changed

src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Emitters/ValidationsGenerator.Emitter.cs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,13 @@
77
using System.Text;
88
using Microsoft.CodeAnalysis.CSharp;
99
using System.IO;
10-
using System.Text.RegularExpressions;
1110

1211
namespace Microsoft.AspNetCore.Http.ValidationsGenerator;
1312

1413
public sealed partial class ValidationsGenerator : IIncrementalGenerator
1514
{
1615
public static string GeneratedCodeConstructor => $@"global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{typeof(ValidationsGenerator).Assembly.FullName}"", ""{typeof(ValidationsGenerator).Assembly.GetName().Version}"")";
1716
public static string GeneratedCodeAttribute => $"[{GeneratedCodeConstructor}]";
18-
private static readonly Regex InvalidNameCharsRegex = new("[^0-9A-Za-z_]", RegexOptions.Compiled);
1917

2018
internal static void Emit(SourceProductionContext context, (InterceptableLocation? AddValidation, ImmutableArray<ValidatableType> ValidatableTypes) emitInputs)
2119
{
@@ -102,8 +100,6 @@ public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterIn
102100
validatableInfo = null;
103101
return false;
104102
}
105-
106-
{{EmitCreateMethods(validatableTypes)}}
107103
}
108104
109105
{{GeneratedCodeAttribute}}
@@ -186,24 +182,9 @@ private static string EmitTypeChecks(ImmutableArray<ValidatableType> validatable
186182
var typeName = validatableType.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
187183
cw.WriteLine($"if (type == typeof({typeName}))");
188184
cw.StartBlock();
189-
cw.WriteLine($"validatableInfo = Create{SanitizeTypeName(validatableType.Type.MetadataName)}();");
190-
cw.WriteLine("return true;");
191-
cw.EndBlock();
192-
}
193-
return sw.ToString();
194-
}
195-
196-
private static string EmitCreateMethods(ImmutableArray<ValidatableType> validatableTypes)
197-
{
198-
var sw = new StringWriter();
199-
var cw = new CodeWriter(sw, baseIndent: 2);
200-
foreach (var validatableType in validatableTypes)
201-
{
202-
cw.WriteLine($@"private ValidatableTypeInfo Create{SanitizeTypeName(validatableType.Type.MetadataName)}()");
203-
cw.StartBlock();
204-
cw.WriteLine("return new GeneratedValidatableTypeInfo(");
185+
cw.WriteLine($"validatableInfo = new GeneratedValidatableTypeInfo(");
205186
cw.Indent++;
206-
cw.WriteLine($"type: typeof({validatableType.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}),");
187+
cw.WriteLine($"type: typeof({typeName}),");
207188
if (validatableType.Members.IsDefaultOrEmpty)
208189
{
209190
cw.WriteLine("members: []");
@@ -221,6 +202,7 @@ private static string EmitCreateMethods(ImmutableArray<ValidatableType> validata
221202
}
222203
cw.Indent--;
223204
cw.WriteLine(");");
205+
cw.WriteLine("return true;");
224206
cw.EndBlock();
225207
}
226208
return sw.ToString();
@@ -237,10 +219,4 @@ private static void EmitValidatableMemberForCreate(ValidatableProperty member, C
237219
cw.Indent--;
238220
cw.WriteLine("),");
239221
}
240-
241-
private static string SanitizeTypeName(string typeName)
242-
{
243-
// Replace invalid characters with underscores
244-
return InvalidNameCharsRegex.Replace(typeName, "_");
245-
}
246222
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Http.ValidationsGenerator.Tests;
5+
6+
public partial class ValidationsGeneratorTests : ValidationsGeneratorTestBase
7+
{
8+
[Fact]
9+
public async Task CanValidateMultipleNamespaces()
10+
{
11+
// Arrange
12+
var source = """
13+
using System;
14+
using System.ComponentModel.DataAnnotations;
15+
using System.Collections.Generic;
16+
using System.Threading.Tasks;
17+
using Microsoft.AspNetCore.Builder;
18+
using Microsoft.AspNetCore.Http;
19+
using Microsoft.AspNetCore.Http.Validation;
20+
using Microsoft.AspNetCore.Routing;
21+
using Microsoft.Extensions.DependencyInjection;
22+
23+
var builder = WebApplication.CreateBuilder();
24+
25+
builder.Services.AddValidation();
26+
27+
var app = builder.Build();
28+
29+
app.MapPost("/namespace-one", (NamespaceOne.Type obj) => Results.Ok("Passed"));
30+
app.MapPost("/namespace-two", (NamespaceTwo.Type obj) => Results.Ok("Passed"));
31+
32+
app.Run();
33+
34+
namespace NamespaceOne {
35+
public class Type
36+
{
37+
[StringLength(10)]
38+
public string StringWithLength { get; set; } = string.Empty;
39+
}
40+
}
41+
42+
namespace NamespaceTwo {
43+
public class Type
44+
{
45+
[StringLength(20)]
46+
public string StringWithLength { get; set; } = string.Empty;
47+
}
48+
}
49+
""";
50+
await Verify(source, out var compilation);
51+
await VerifyEndpoint(compilation, "/namespace-one", async (endpoint, serviceProvider) =>
52+
{
53+
await InvalidStringWithLengthProducesError(endpoint);
54+
await ValidInputProducesNoWarnings(endpoint);
55+
56+
async Task InvalidStringWithLengthProducesError(Endpoint endpoint)
57+
{
58+
var payload = """
59+
{
60+
"StringWithLength": "abcdefghijk"
61+
}
62+
""";
63+
var context = CreateHttpContextWithPayload(payload, serviceProvider);
64+
65+
await endpoint.RequestDelegate(context);
66+
67+
var problemDetails = await AssertBadRequest(context);
68+
Assert.Collection(problemDetails.Errors, kvp =>
69+
{
70+
Assert.Equal("StringWithLength", kvp.Key);
71+
Assert.Equal("The field StringWithLength must be a string with a maximum length of 10.", kvp.Value.Single());
72+
});
73+
}
74+
75+
async Task ValidInputProducesNoWarnings(Endpoint endpoint)
76+
{
77+
var payload = """
78+
{
79+
"StringWithLength": "abc"
80+
}
81+
""";
82+
var context = CreateHttpContextWithPayload(payload, serviceProvider);
83+
await endpoint.RequestDelegate(context);
84+
85+
Assert.Equal(200, context.Response.StatusCode);
86+
}
87+
});
88+
await VerifyEndpoint(compilation, "/namespace-two", async (endpoint, serviceProvider) =>
89+
{
90+
await InvalidStringWithLengthProducesError(endpoint);
91+
await ValidInputProducesNoWarnings(endpoint);
92+
93+
async Task InvalidStringWithLengthProducesError(Endpoint endpoint)
94+
{
95+
var payload = """
96+
{
97+
"StringWithLength": "abcdefghijklmnopqrstu"
98+
}
99+
""";
100+
var context = CreateHttpContextWithPayload(payload, serviceProvider);
101+
102+
await endpoint.RequestDelegate(context);
103+
104+
var problemDetails = await AssertBadRequest(context);
105+
Assert.Collection(problemDetails.Errors, kvp =>
106+
{
107+
Assert.Equal("StringWithLength", kvp.Key);
108+
Assert.Equal("The field StringWithLength must be a string with a maximum length of 20.", kvp.Value.Single());
109+
});
110+
}
111+
112+
async Task ValidInputProducesNoWarnings(Endpoint endpoint)
113+
{
114+
var payload = """
115+
{
116+
"StringWithLength": "abcdefghijk"
117+
}
118+
""";
119+
var context = CreateHttpContextWithPayload(payload, serviceProvider);
120+
await endpoint.RequestDelegate(context);
121+
122+
Assert.Equal(200, context.Response.StatusCode);
123+
}
124+
});
125+
}
126+
}

0 commit comments

Comments
 (0)