Skip to content

Commit

Permalink
Separate Generated Files (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
viceroypenguin authored Mar 21, 2024
1 parent 40a4248 commit 8c45c33
Show file tree
Hide file tree
Showing 85 changed files with 1,208 additions and 593 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ public sealed partial class ImmediateApisGenerator
{
private sealed record Method
{
public required string HttpMethod { get; init; }
public required string Route { get; init; }

public required string ClassName { get; init; }
public required string MethodName { get; init; }
public required string ClassAsMethodName { get; init; }
public required string ParameterType { get; init; }

public required bool AllowAnonymous { get; init; }
public required bool Authorize { get; init; }
public required string? AuthorizePolicy { get; init; }
Expand Down
26 changes: 24 additions & 2 deletions src/Immediate.Apis.Generators/ImmediateApisGenerator.Render.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Scriban;

namespace Immediate.Apis.Generators;

public sealed partial class ImmediateApisGenerator
{
private static void RenderMethod(
SourceProductionContext context,
Method method,
string assemblyName,
Template template
)
{
var token = context.CancellationToken;

token.ThrowIfCancellationRequested();

var source = template.Render(new
{
Assembly = assemblyName,
Method = method,
});

token.ThrowIfCancellationRequested();
context.AddSource($"RouteBuilder.{method.ClassAsMethodName}.g.cs", source);
}

private static void RenderMethods(
SourceProductionContext context,
ImmutableArray<Method> methods,
string assemblyName
string assemblyName,
Template template
)
{
if (methods.Length == 0)
return;

var token = context.CancellationToken;

var template = Utility.GetTemplate("Routes");
token.ThrowIfCancellationRequested();

var source = template.Render(new
Expand Down
145 changes: 78 additions & 67 deletions src/Immediate.Apis.Generators/ImmediateApisGenerator.Transform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,99 +12,110 @@ CancellationToken token
token.ThrowIfCancellationRequested();

var symbol = (INamedTypeSymbol)context.TargetSymbol;
var displayName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var attributes = symbol.GetAttributes();
var attributeNames = attributes
.Select(a => a.AttributeClass?.ToString() ?? "")
.ToList();

token.ThrowIfCancellationRequested();

if (symbol.ContainingType is not null)
if (GetMethodAttributeIndex(attributeNames) is not { } methodIndex)
return null;

if (GetValidHandleMethod(symbol) is not { } handleMethod)
return null;

token.ThrowIfCancellationRequested();

if (symbol
.GetMembers()
.OfType<IMethodSymbol>()
.Where(m => m.IsStatic)
.Where(m =>
m.Name.Equals("Handle", StringComparison.Ordinal)
|| m.Name.Equals("HandleAsync", StringComparison.Ordinal)
)
.ToList() is not [var handleMethod])
{
return null;
}
var attribute = attributes[methodIndex];
var httpMethod = attribute.AttributeClass!.Name[..^9];
var route = (string?)attribute.ConstructorArguments.FirstOrDefault().Value;

// must have request type and cancellation token
if (handleMethod.Parameters.Length < 2)
if (route == null)
return null;

token.ThrowIfCancellationRequested();

var requestType = handleMethod.Parameters[0].Type
.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

token.ThrowIfCancellationRequested();
var allowAnonymous = attributeNames.Contains("Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute");

var attributeNames = symbol.GetAttributes()
.Select(a => a.AttributeClass?.ToString() ?? "")
.ToList();
var authorizeIndex = attributeNames.IndexOf("Microsoft.AspNetCore.Authorization.AuthorizeAttribute");
var authorize = authorizeIndex >= 0;
var authorizePolicy = string.Empty;

foreach (var methodName in s_methodAttributes)
if (authorize)
{
var methodIndex = attributeNames.IndexOf(methodName);
if (methodIndex < 0)
continue;
var authorizeAttribute = attributes[authorizeIndex];
if (authorizeAttribute.ConstructorArguments.Length > 0)
{
authorizePolicy = (string)authorizeAttribute.ConstructorArguments[0].Value!;
}
else if (authorizeAttribute.NamedArguments.Length > 0)
{
foreach (var argument in authorizeAttribute.NamedArguments)
{
if (argument.Key != "Policy")
return null;

token.ThrowIfCancellationRequested();
authorizePolicy = (string)argument.Value.Value!;
}
}
}

var attribute = symbol.GetAttributes()[methodIndex];
var method = attribute.AttributeClass!.Name[..^9];
var route = (string?)attribute.ConstructorArguments.FirstOrDefault().Value;
token.ThrowIfCancellationRequested();

if (route == null)
return null;
var className = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var classAsMethodName = symbol.ToString().Replace(".", "_");
var parameterType = handleMethod.Parameters[0].Type
.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

token.ThrowIfCancellationRequested();
token.ThrowIfCancellationRequested();

var allowAnonymous = attributeNames.Contains("Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute");
return new()
{
HttpMethod = httpMethod,
Route = route,

var authorizeIndex = attributeNames.IndexOf("Microsoft.AspNetCore.Authorization.AuthorizeAttribute");
var authorize = authorizeIndex >= 0;
var authorizePolicy = string.Empty;
ClassName = className,
ClassAsMethodName = classAsMethodName,
ParameterType = parameterType,

if (authorize)
{
var authorizeAttribute = symbol.GetAttributes()[authorizeIndex];
if (authorizeAttribute.ConstructorArguments.Length > 0)
{
authorizePolicy = (string)authorizeAttribute.ConstructorArguments[0].Value!;
}
else if (authorizeAttribute.NamedArguments.Length > 0)
{
foreach (var argument in authorizeAttribute.NamedArguments)
{
if (argument.Key != "Policy")
return null;
AllowAnonymous = allowAnonymous,
Authorize = authorize,
AuthorizePolicy = authorizePolicy
};
}

authorizePolicy = (string)argument.Value.Value!;
}
}
}
private static int? GetMethodAttributeIndex(List<string> attributeNames)
{
foreach (var name in s_methodAttributes)
{
var index = attributeNames.IndexOf(name);
if (index >= 0)
return index;
}

token.ThrowIfCancellationRequested();
return null;
}

return new()
{
Route = route,
ClassName = displayName,
MethodName = method,
ParameterType = requestType,
AllowAnonymous = allowAnonymous,
Authorize = authorize,
AuthorizePolicy = authorizePolicy
};
private static IMethodSymbol? GetValidHandleMethod(INamedTypeSymbol symbol)
{
if (symbol
.GetMembers()
.OfType<IMethodSymbol>()
.Where(m => m.IsStatic)
.Where(m =>
m.Name.Equals("Handle", StringComparison.Ordinal)
|| m.Name.Equals("HandleAsync", StringComparison.Ordinal)
)
.ToList() is not [var handleMethod])
{
return null;
}

return null;
// must have request type and cancellation token
if (handleMethod.Parameters.Length < 2)
return null;

return handleMethod;
}
}
12 changes: 9 additions & 3 deletions src/Immediate.Apis.Generators/ImmediateApisGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
(_, _) => true,
TransformMethod
)
.Where(m => m != null)
.Collect()!;
.Where(m => m != null);

var assemblyName = context.CompilationProvider
.Select((cp, _) => cp.AssemblyName!
Expand All @@ -23,9 +22,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.Trim()
);

var perMethodTemplate = Utility.GetTemplate("Route");
context.RegisterSourceOutput(
methods.Combine(assemblyName),
(spc, m) => RenderMethods(spc, m.Left!, m.Right)
(spc, m) => RenderMethod(spc, m.Left!, m.Right, perMethodTemplate)
);

var allMethodsTemplate = Utility.GetTemplate("Routes");
context.RegisterSourceOutput(
methods.Collect().Combine(assemblyName),
(spc, m) => RenderMethods(spc, m.Left!, m.Right, allMethodsTemplate)
);
}
}
28 changes: 28 additions & 0 deletions src/Immediate.Apis.Generators/Templates/Route.sbntxt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Mvc;

#pragma warning disable CS1591

namespace Microsoft.AspNetCore.Builder;

public static partial class {{ assembly }}RoutesBuilder
{
private static void Map{{ method.class_as_method_name }}Endpoint(IEndpointRouteBuilder app)
{
var endpoint = app
.{{ method.http_method }}(
"{{ method.route }}",
async (
[AsParameters] {{ method.parameter_type }} parameters,
[FromServices] {{ method.class_name }}.Handler handler,
CancellationToken token
) => await handler.HandleAsync(parameters, token)
);


{{~ if method.allow_anonymous ~}}
_ = endpoint.AllowAnonymous();
{{~ else if method.authorize ~}}
_ = endpoint.RequireAuthorization({{ if !string.empty method.authorize_policy }}"{{ method.authorize_policy }}"{{ end }})
{{~ end ~}}
}
}
20 changes: 3 additions & 17 deletions src/Immediate.Apis.Generators/Templates/Routes.sbntxt
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,16 @@ using Microsoft.AspNetCore.Mvc;

namespace Microsoft.AspNetCore.Builder;

public static class {{ assembly }}RoutesBuilder
public static partial class {{ assembly }}RoutesBuilder
{
public static IEndpointRouteBuilder Map{{ assembly }}Endpoints(
this IEndpointRouteBuilder app
)
{
{{~ for m in methods ~}}
_ = app
.{{ m.method_name }}(
"{{ m.route }}",
async (
[AsParameters] {{ m.parameter_type }} parameters,
[FromServices] {{ m.class_name }}.Handler handler,
CancellationToken token
) => await handler.HandleAsync(parameters, token)
)
{{~ if m.allow_anonymous ~}}
.AllowAnonymous()
{{~ else if m.authorize ~}}
.RequireAuthorization({{ if !string.empty m.authorize_policy }}"{{ m.authorize_policy }}"{{ end }})
{{~ end ~}}
;

Map{{ m.class_as_method_name }}Endpoint(app);
{{~ end ~}}

return app;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//HintName: RouteBuilder.Dummy_GetUsersQuery.g.cs
using Microsoft.AspNetCore.Mvc;

#pragma warning disable CS1591

namespace Microsoft.AspNetCore.Builder;

public static partial class TestsRoutesBuilder
{
private static void MapDummy_GetUsersQueryEndpoint(IEndpointRouteBuilder app)
{
var endpoint = app
.MapDelete(
"/test",
async (
[AsParameters] global::Dummy.GetUsersQuery.Query parameters,
[FromServices] global::Dummy.GetUsersQuery.Handler handler,
CancellationToken token
) => await handler.HandleAsync(parameters, token)
);


_ = endpoint.AllowAnonymous();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,13 @@

namespace Microsoft.AspNetCore.Builder;

public static class TestsRoutesBuilder
public static partial class TestsRoutesBuilder
{
public static IEndpointRouteBuilder MapTestsEndpoints(
this IEndpointRouteBuilder app
)
{
_ = app
.MapDelete(
"/test",
async (
[AsParameters] global::Dummy.GetUsersQuery.Query parameters,
[FromServices] global::Dummy.GetUsersQuery.Handler handler,
CancellationToken token
) => await handler.HandleAsync(parameters, token)
)
.AllowAnonymous()
;
MapDummy_GetUsersQueryEndpoint(app);

return app;
}
Expand Down
Loading

0 comments on commit 8c45c33

Please sign in to comment.