Skip to content

Commit 5b32db8

Browse files
authored
Refactor HTTP attribute definitions (#22)
1 parent b02c980 commit 5b32db8

File tree

1 file changed

+75
-140
lines changed

1 file changed

+75
-140
lines changed

src/GeneratedEndpoints/MinimalApiGenerator.cs

Lines changed: 75 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -15,45 +15,22 @@ public sealed class MinimalApiGenerator : IIncrementalGenerator
1515
private const string BaseNamespace = "Microsoft.AspNetCore.Generated";
1616
private const string AttributesNamespace = $"{BaseNamespace}.Attributes";
1717

18-
private const string MapGetAttributeName = "MapGetAttribute";
19-
private const string MapGetAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapGetAttributeName}";
20-
private const string MapGetAttributeHint = $"{MapGetAttributeFullyQualifiedName}.gs.cs";
21-
22-
private const string MapPostAttributeName = "MapPostAttribute";
23-
private const string MapPostAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapPostAttributeName}";
24-
private const string MapPostAttributeHint = $"{MapPostAttributeFullyQualifiedName}.gs.cs";
25-
26-
private const string MapPutAttributeName = "MapPutAttribute";
27-
private const string MapPutAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapPutAttributeName}";
28-
private const string MapPutAttributeHint = $"{MapPutAttributeFullyQualifiedName}.gs.cs";
29-
30-
private const string MapDeleteAttributeName = "MapDeleteAttribute";
31-
private const string MapDeleteAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapDeleteAttributeName}";
32-
private const string MapDeleteAttributeHint = $"{MapDeleteAttributeFullyQualifiedName}.gs.cs";
33-
34-
private const string MapOptionsAttributeName = "MapOptionsAttribute";
35-
private const string MapOptionsAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapOptionsAttributeName}";
36-
private const string MapOptionsAttributeHint = $"{MapOptionsAttributeFullyQualifiedName}.gs.cs";
37-
38-
private const string MapHeadAttributeName = "MapHeadAttribute";
39-
private const string MapHeadAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapHeadAttributeName}";
40-
private const string MapHeadAttributeHint = $"{MapHeadAttributeFullyQualifiedName}.gs.cs";
41-
42-
private const string MapPatchAttributeName = "MapPatchAttribute";
43-
private const string MapPatchAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapPatchAttributeName}";
44-
private const string MapPatchAttributeHint = $"{MapPatchAttributeFullyQualifiedName}.gs.cs";
45-
46-
private const string MapQueryAttributeName = "MapQueryAttribute";
47-
private const string MapQueryAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapQueryAttributeName}";
48-
private const string MapQueryAttributeHint = $"{MapQueryAttributeFullyQualifiedName}.gs.cs";
49-
50-
private const string MapTraceAttributeName = "MapTraceAttribute";
51-
private const string MapTraceAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapTraceAttributeName}";
52-
private const string MapTraceAttributeHint = $"{MapTraceAttributeFullyQualifiedName}.gs.cs";
53-
54-
private const string MapConnectAttributeName = "MapConnectAttribute";
55-
private const string MapConnectAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapConnectAttributeName}";
56-
private const string MapConnectAttributeHint = $"{MapConnectAttributeFullyQualifiedName}.gs.cs";
18+
private static readonly ImmutableArray<HttpAttributeDefinition> HttpAttributeDefinitions =
19+
[
20+
CreateHttpAttributeDefinition("MapGetAttribute", "GET"),
21+
CreateHttpAttributeDefinition("MapPostAttribute", "POST"),
22+
CreateHttpAttributeDefinition("MapPutAttribute", "PUT"),
23+
CreateHttpAttributeDefinition("MapPatchAttribute", "PATCH"),
24+
CreateHttpAttributeDefinition("MapDeleteAttribute", "DELETE"),
25+
CreateHttpAttributeDefinition("MapOptionsAttribute", "OPTIONS"),
26+
CreateHttpAttributeDefinition("MapHeadAttribute", "HEAD"),
27+
CreateHttpAttributeDefinition("MapQueryAttribute", "QUERY"),
28+
CreateHttpAttributeDefinition("MapTraceAttribute", "TRACE"),
29+
CreateHttpAttributeDefinition("MapConnectAttribute", "CONNECT"),
30+
];
31+
32+
private static readonly ImmutableDictionary<string, HttpAttributeDefinition> HttpAttributeDefinitionsByName =
33+
HttpAttributeDefinitions.ToImmutableDictionary(static definition => definition.Name);
5734

5835
private const string NameAttributeNamedParameter = "Name";
5936
private const string SummaryAttributeNamedParameter = "Summary";
@@ -116,103 +93,55 @@ public sealed class MinimalApiGenerator : IIncrementalGenerator
11693
#nullable enable
11794
""";
11895

96+
private static HttpAttributeDefinition CreateHttpAttributeDefinition(string attributeName, string verb)
97+
{
98+
var fullyQualifiedName = $"{AttributesNamespace}.{attributeName}";
99+
return new HttpAttributeDefinition(attributeName, fullyQualifiedName, $"{fullyQualifiedName}.gs.cs", verb);
100+
}
101+
119102
public void Initialize(IncrementalGeneratorInitializationContext context)
120103
{
121104
context.RegisterPostInitializationOutput(RegisterAttributes);
122105

123-
var getRequestHandlers = context.SyntaxProvider
124-
.ForAttributeWithMetadataName(MapGetAttributeFullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
125-
.WhereNotNull()
126-
.Collect();
127-
128-
var postRequestHandlers = context.SyntaxProvider
129-
.ForAttributeWithMetadataName(MapPostAttributeFullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
130-
.WhereNotNull()
131-
.Collect();
132-
133-
var putRequestHandlers = context.SyntaxProvider
134-
.ForAttributeWithMetadataName(MapPutAttributeFullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
135-
.WhereNotNull()
136-
.Collect();
137-
138-
var deleteRequestHandlers = context.SyntaxProvider
139-
.ForAttributeWithMetadataName(MapDeleteAttributeFullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
140-
.WhereNotNull()
141-
.Collect();
142-
143-
var optionsRequestHandlers = context.SyntaxProvider
144-
.ForAttributeWithMetadataName(MapOptionsAttributeFullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
145-
.WhereNotNull()
146-
.Collect();
147-
148-
var headRequestHandlers = context.SyntaxProvider
149-
.ForAttributeWithMetadataName(MapHeadAttributeFullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
150-
.WhereNotNull()
151-
.Collect();
152-
153-
var patchRequestHandlers = context.SyntaxProvider
154-
.ForAttributeWithMetadataName(MapPatchAttributeFullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
155-
.WhereNotNull()
156-
.Collect();
157-
158-
var queryRequestHandlers = context.SyntaxProvider
159-
.ForAttributeWithMetadataName(MapQueryAttributeFullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
160-
.WhereNotNull()
161-
.Collect();
162-
163-
var traceRequestHandlers = context.SyntaxProvider
164-
.ForAttributeWithMetadataName(MapTraceAttributeFullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
165-
.WhereNotNull()
166-
.Collect();
167-
168-
var connectRequestHandlers = context.SyntaxProvider
169-
.ForAttributeWithMetadataName(MapConnectAttributeFullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
170-
.WhereNotNull()
171-
.Collect();
172-
173-
var requestHandlers = getRequestHandlers.Combine(postRequestHandlers)
174-
.Select(static (x, _) => x.Left.AddRange(x.Right))
175-
.Combine(putRequestHandlers)
176-
.Select(static (x, _) => x.Left.AddRange(x.Right))
177-
.Combine(patchRequestHandlers)
178-
.Select(static (x, _) => x.Left.AddRange(x.Right))
179-
.Combine(deleteRequestHandlers)
180-
.Select(static (x, _) => x.Left.AddRange(x.Right))
181-
.Combine(optionsRequestHandlers)
182-
.Select(static (x, _) => x.Left.AddRange(x.Right))
183-
.Combine(headRequestHandlers)
184-
.Select(static (x, _) => x.Left.AddRange(x.Right))
185-
.Combine(queryRequestHandlers)
186-
.Select(static (x, _) => x.Left.AddRange(x.Right))
187-
.Combine(traceRequestHandlers)
188-
.Select(static (x, _) => x.Left.AddRange(x.Right))
189-
.Combine(connectRequestHandlers)
190-
.Select(static (x, _) => x.Left.AddRange(x.Right));
106+
var requestHandlerProviders = ImmutableArray.CreateBuilder<IncrementalValueProvider<ImmutableArray<RequestHandler>>>(
107+
HttpAttributeDefinitions.Length);
108+
109+
foreach (var definition in HttpAttributeDefinitions)
110+
{
111+
var handlers = context.SyntaxProvider
112+
.ForAttributeWithMetadataName(definition.FullyQualifiedName, RequestHandlerFilter, RequestHandlerTransform)
113+
.WhereNotNull()
114+
.Collect();
115+
116+
requestHandlerProviders.Add(handlers);
117+
}
118+
119+
var requestHandlers = CombineRequestHandlers(requestHandlerProviders.MoveToImmutable());
191120

192121
context.RegisterSourceOutput(requestHandlers, GenerateSource);
193122
}
194123

195-
private static void RegisterAttributes(IncrementalGeneratorPostInitializationContext context)
124+
private static IncrementalValueProvider<ImmutableArray<RequestHandler>> CombineRequestHandlers(
125+
ImmutableArray<IncrementalValueProvider<ImmutableArray<RequestHandler>>> handlerProviders)
196126
{
197-
// Definitions for HTTP method attributes
198-
var httpAttributes = new[]
127+
if (handlerProviders.IsDefaultOrEmpty)
128+
throw new InvalidOperationException("No HTTP attribute definitions were provided.");
129+
130+
var combined = handlerProviders[0];
131+
for (var i = 1; i < handlerProviders.Length; i++)
199132
{
200-
(Name: MapGetAttributeName, FullyQualified: MapGetAttributeFullyQualifiedName, Hint: MapGetAttributeHint, Verb: "GET"),
201-
(Name: MapPostAttributeName, FullyQualified: MapPostAttributeFullyQualifiedName, Hint: MapPostAttributeHint, Verb: "POST"),
202-
(Name: MapPutAttributeName, FullyQualified: MapPutAttributeFullyQualifiedName, Hint: MapPutAttributeHint, Verb: "PUT"),
203-
(Name: MapDeleteAttributeName, FullyQualified: MapDeleteAttributeFullyQualifiedName, Hint: MapDeleteAttributeHint, Verb: "DELETE"),
204-
(Name: MapOptionsAttributeName, FullyQualified: MapOptionsAttributeFullyQualifiedName, Hint: MapOptionsAttributeHint, Verb: "OPTIONS"),
205-
(Name: MapHeadAttributeName, FullyQualified: MapHeadAttributeFullyQualifiedName, Hint: MapHeadAttributeHint, Verb: "HEAD"),
206-
(Name: MapPatchAttributeName, FullyQualified: MapPatchAttributeFullyQualifiedName, Hint: MapPatchAttributeHint, Verb: "PATCH"),
207-
(Name: MapQueryAttributeName, FullyQualified: MapQueryAttributeFullyQualifiedName, Hint: MapQueryAttributeHint, Verb: "QUERY"),
208-
(Name: MapTraceAttributeName, FullyQualified: MapTraceAttributeFullyQualifiedName, Hint: MapTraceAttributeHint, Verb: "TRACE"),
209-
(Name: MapConnectAttributeName, FullyQualified: MapConnectAttributeFullyQualifiedName, Hint: MapConnectAttributeHint, Verb: "CONNECT"),
210-
};
133+
combined = combined.Combine(handlerProviders[i]).Select(static (x, _) => x.Left.AddRange(x.Right));
134+
}
211135

212-
foreach (var (name, _, hint, verb) in httpAttributes)
136+
return combined;
137+
}
138+
139+
private static void RegisterAttributes(IncrementalGeneratorPostInitializationContext context)
140+
{
141+
foreach (var definition in HttpAttributeDefinitions)
213142
{
214-
var source = GenerateHttpAttributeSource(FileHeader, AttributesNamespace, name, verb);
215-
context.AddSource(hint, SourceText.From(source, Encoding.UTF8));
143+
var source = GenerateHttpAttributeSource(FileHeader, AttributesNamespace, definition.Name, definition.Verb);
144+
context.AddSource(definition.Hint, SourceText.From(source, Encoding.UTF8));
216145
}
217146

218147
// RequireAuthorization
@@ -646,20 +575,9 @@ CancellationToken cancellationToken
646575

647576
var attributeName = attribute.AttributeClass?.Name ?? "";
648577

649-
var httpMethod = attributeName switch
650-
{
651-
MapGetAttributeName => "Get",
652-
MapPostAttributeName => "Post",
653-
MapPutAttributeName => "Put",
654-
MapDeleteAttributeName => "Delete",
655-
MapOptionsAttributeName => "OPTIONS",
656-
MapHeadAttributeName => "HEAD",
657-
MapPatchAttributeName => "Patch",
658-
MapQueryAttributeName => "QUERY",
659-
MapTraceAttributeName => "TRACE",
660-
MapConnectAttributeName => "CONNECT",
661-
_ => "",
662-
};
578+
var httpMethod = HttpAttributeDefinitionsByName.TryGetValue(attributeName, out var definition)
579+
? definition.Verb
580+
: "";
663581

664582
var pattern = (attribute.ConstructorArguments.Length > 0 ? attribute.ConstructorArguments[0].Value as string : "") ?? "";
665583

@@ -1432,13 +1350,15 @@ private static void GenerateMapRequestHandler(StringBuilder source, RequestHandl
14321350
source.AppendLine("(");
14331351
}
14341352

1353+
var mapMethodSuffix = GetMapMethodSuffix(requestHandler.HttpMethod);
1354+
14351355
source.Append(indent);
14361356
source.Append("builder.Map");
1437-
source.Append(requestHandler.HttpMethod is "Get" or "Post" or "Put" or "Delete" or "Patch" ? requestHandler.HttpMethod : "Methods");
1357+
source.Append(mapMethodSuffix ?? "Methods");
14381358
source.Append('(');
14391359
source.Append(StringLiteral(requestHandler.Pattern));
14401360
source.Append(", ");
1441-
if (requestHandler.HttpMethod is "OPTIONS" or "HEAD" or "TRACE" or "CONNECT" or "QUERY")
1361+
if (mapMethodSuffix is null)
14421362
{
14431363
source.Append("new[] { \"");
14441364
source.Append(requestHandler.HttpMethod);
@@ -1638,6 +1558,19 @@ private static void GenerateMapRequestHandler(StringBuilder source, RequestHandl
16381558
}
16391559
}
16401560

1561+
private static string? GetMapMethodSuffix(string httpMethod)
1562+
{
1563+
return httpMethod switch
1564+
{
1565+
"GET" => "Get",
1566+
"POST" => "Post",
1567+
"PUT" => "Put",
1568+
"DELETE" => "Delete",
1569+
"PATCH" => "Patch",
1570+
_ => null,
1571+
};
1572+
}
1573+
16411574
private static string GetBindingSourceAttribute(BindingSource source, string? key)
16421575
{
16431576
return source switch
@@ -1900,6 +1833,8 @@ _ when char.IsControl(c) => "\\u" + ((int)c).ToString("x4", CultureInfo.Invarian
19001833
};
19011834
}
19021835

1836+
private readonly record struct HttpAttributeDefinition(string Name, string FullyQualifiedName, string Hint, string Verb);
1837+
19031838
private readonly record struct RequestHandler(
19041839
RequestHandlerClass Class,
19051840
RequestHandlerMethod Method,

0 commit comments

Comments
 (0)