Skip to content

Commit

Permalink
Add support for attributes on target method
Browse files Browse the repository at this point in the history
  • Loading branch information
viceroypenguin committed Nov 30, 2024
1 parent e49ae95 commit 61cee46
Show file tree
Hide file tree
Showing 25 changed files with 865 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ private sealed record Method
{
public required string MapMethod { get; init; }
public required string? HttpMethod { get; init; }
public required EquatableReadOnlyList<string> Attributes { get; init; }
public required string ParameterAttribute { get; init; }
public required string Route { get; init; }

Expand Down
43 changes: 43 additions & 0 deletions src/Immediate.Apis.Generators/ImmediateApisGenerator.Transform.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Immediate.Apis.Generators;

Expand Down Expand Up @@ -73,6 +75,10 @@ CancellationToken token

token.ThrowIfCancellationRequested();

var handleMethodAttributes = GetHandleMethodAttributes(handleMethod);

token.ThrowIfCancellationRequested();

var useCustomization = HasCustomizationMethod(symbol);

token.ThrowIfCancellationRequested();
Expand All @@ -85,6 +91,7 @@ CancellationToken token
{
MapMethod = mapMethod,
HttpMethod = httpMethod,
Attributes = handleMethodAttributes,
ParameterAttribute = parameterAttribute,
Route = route,

Expand Down Expand Up @@ -217,4 +224,40 @@ private static string GetMapMethod(AttributeData attributeData)
.Value
!.ToString();
}

private static EquatableReadOnlyList<string> GetHandleMethodAttributes(IMethodSymbol methodSymbol) =>
methodSymbol.GetAttributes()
.Select(GetAttributeString)
.ToEquatableReadOnlyList();

private static string GetAttributeString(AttributeData attributeData)
{
var @class = attributeData.AttributeClass!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

var parameters = new List<string>();

foreach (var tc in attributeData.ConstructorArguments)
{
if (GetTypedConstantString(tc) is { } str)
parameters.Add(str);
}

foreach (var na in attributeData.NamedArguments)
{
if (GetTypedConstantString(na.Value) is { } str)
parameters.Add($"{na.Key} = {str}");

Check warning on line 248 in src/Immediate.Apis.Generators/ImmediateApisGenerator.Transform.cs

View check run for this annotation

Codecov / codecov/patch

src/Immediate.Apis.Generators/ImmediateApisGenerator.Transform.cs#L248

Added line #L248 was not covered by tests
}

return parameters.Count == 0
? @class
: $"{@class}({string.Join(", ", parameters)})";
}

[SuppressMessage("Style", "IDE0072:Add missing cases")]
private static string? GetTypedConstantString(TypedConstant tc) =>
tc.Kind switch
{
TypedConstantKind.Array => $"[{string.Join(", ", tc.Values.Select(GetTypedConstantString))}]",

Check warning on line 260 in src/Immediate.Apis.Generators/ImmediateApisGenerator.Transform.cs

View check run for this annotation

Codecov / codecov/patch

src/Immediate.Apis.Generators/ImmediateApisGenerator.Transform.cs#L260

Added line #L260 was not covered by tests
_ => tc.ToCSharpString(),
};
}
3 changes: 3 additions & 0 deletions src/Immediate.Apis.Generators/Templates/Route.sbntxt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ namespace Microsoft.AspNetCore.Builder
{{~ if !string.empty method.http_method ~}}
["{{ method.http_method }}"],
{{~ end ~}}
{{~ for a in method.attributes ~}}
[{{ a }}]
{{~ end ~}}
async (
[{{ method.parameter_attribute }}] {{ method.parameter_type }} parameters,
[FromServices] {{ method.class_full_name }}.Handler handler,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Immediate.Apis.Shared;
using Immediate.Handlers.Shared;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Immediate.Apis.FunctionalTests.Features.WeatherForecast;

Expand All @@ -27,6 +28,8 @@ public sealed record Result
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

[ProducesResponseType<IReadOnlyList<Result>>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
private static async ValueTask<IReadOnlyList<Result>> Handle(
Query _,
CancellationToken token
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//HintName: IH.Dummy.GetUsersQuery.g.cs
using Microsoft.Extensions.DependencyInjection;

#pragma warning disable CS1591

namespace Dummy;

partial class GetUsersQuery
{
public sealed partial class Handler : global::Immediate.Handlers.Shared.IHandler<global::Dummy.GetUsersQuery.Query, int>
{
private readonly global::Dummy.GetUsersQuery.HandleBehavior _handleBehavior;

public Handler(
global::Dummy.GetUsersQuery.HandleBehavior handleBehavior
)
{
var handlerType = typeof(GetUsersQuery);

_handleBehavior = handleBehavior;

}

public async global::System.Threading.Tasks.ValueTask<int> HandleAsync(
global::Dummy.GetUsersQuery.Query request,
global::System.Threading.CancellationToken cancellationToken = default
)
{
return await _handleBehavior
.HandleAsync(request, cancellationToken)
.ConfigureAwait(false);
}
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public sealed class HandleBehavior : global::Immediate.Handlers.Shared.Behavior<global::Dummy.GetUsersQuery.Query, int>
{

public HandleBehavior(
)
{
}

public override async global::System.Threading.Tasks.ValueTask<int> HandleAsync(
global::Dummy.GetUsersQuery.Query request,
global::System.Threading.CancellationToken cancellationToken
)
{
return await global::Dummy.GetUsersQuery
.HandleAsync(
request
, cancellationToken
)
.ConfigureAwait(false);
}
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public static IServiceCollection AddHandlers(
IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped
)
{
services.Add(new(typeof(global::Dummy.GetUsersQuery.Handler), typeof(global::Dummy.GetUsersQuery.Handler), lifetime));
services.Add(new(typeof(global::Immediate.Handlers.Shared.IHandler<global::Dummy.GetUsersQuery.Query, int>), typeof(global::Dummy.GetUsersQuery.Handler), lifetime));
services.Add(new(typeof(global::Dummy.GetUsersQuery.HandleBehavior), typeof(global::Dummy.GetUsersQuery.HandleBehavior), lifetime));
return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//HintName: IH.ServiceCollectionExtensions.g.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

#pragma warning disable CS1591

public static class HandlerServiceCollectionExtensions
{
public static IServiceCollection AddTestsBehaviors(
this IServiceCollection services)
{

return services;
}

public static IServiceCollection AddTestsHandlers(
this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped
)
{
global::Dummy.GetUsersQuery.AddHandlers(services, lifetime);

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//HintName: RouteBuilder.Dummy_GetUsersQuery.g.cs
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;

#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",
[global::Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute<int>(200)]
async (
[AsParameters] global::Dummy.GetUsersQuery.Query parameters,
[FromServices] global::Dummy.GetUsersQuery.Handler handler,
CancellationToken token
) =>
{
var ret = await handler.HandleAsync(parameters, token);
return ret;
}
);

_ = endpoint.AllowAnonymous();
}
}
}

namespace Dummy
{

/// <remarks><see cref="global::Dummy.GetUsersQuery.Query" /> registered using <c>[AsParameters]</c></remarks>
partial class GetUsersQuery;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//HintName: RoutesBuilder.g.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;

#pragma warning disable CS1591

namespace Microsoft.AspNetCore.Builder;

public static partial class TestsRoutesBuilder
{
public static IEndpointRouteBuilder MapTestsEndpoints(
this IEndpointRouteBuilder app
)
{
MapDummy_GetUsersQueryEndpoint(app);

return app;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//HintName: IH.Dummy.GetUsersQuery.g.cs
using Microsoft.Extensions.DependencyInjection;

#pragma warning disable CS1591

namespace Dummy;

partial class GetUsersQuery
{
public sealed partial class Handler : global::Immediate.Handlers.Shared.IHandler<global::Dummy.GetUsersQuery.Query, int>
{
private readonly global::Dummy.GetUsersQuery.HandleBehavior _handleBehavior;

public Handler(
global::Dummy.GetUsersQuery.HandleBehavior handleBehavior
)
{
var handlerType = typeof(GetUsersQuery);

_handleBehavior = handleBehavior;

}

public async global::System.Threading.Tasks.ValueTask<int> HandleAsync(
global::Dummy.GetUsersQuery.Query request,
global::System.Threading.CancellationToken cancellationToken = default
)
{
return await _handleBehavior
.HandleAsync(request, cancellationToken)
.ConfigureAwait(false);
}
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public sealed class HandleBehavior : global::Immediate.Handlers.Shared.Behavior<global::Dummy.GetUsersQuery.Query, int>
{

public HandleBehavior(
)
{
}

public override async global::System.Threading.Tasks.ValueTask<int> HandleAsync(
global::Dummy.GetUsersQuery.Query request,
global::System.Threading.CancellationToken cancellationToken
)
{
return await global::Dummy.GetUsersQuery
.HandleAsync(
request
, cancellationToken
)
.ConfigureAwait(false);
}
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public static IServiceCollection AddHandlers(
IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped
)
{
services.Add(new(typeof(global::Dummy.GetUsersQuery.Handler), typeof(global::Dummy.GetUsersQuery.Handler), lifetime));
services.Add(new(typeof(global::Immediate.Handlers.Shared.IHandler<global::Dummy.GetUsersQuery.Query, int>), typeof(global::Dummy.GetUsersQuery.Handler), lifetime));
services.Add(new(typeof(global::Dummy.GetUsersQuery.HandleBehavior), typeof(global::Dummy.GetUsersQuery.HandleBehavior), lifetime));
return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//HintName: IH.ServiceCollectionExtensions.g.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

#pragma warning disable CS1591

public static class HandlerServiceCollectionExtensions
{
public static IServiceCollection AddTestsBehaviors(
this IServiceCollection services)
{

return services;
}

public static IServiceCollection AddTestsHandlers(
this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped
)
{
global::Dummy.GetUsersQuery.AddHandlers(services, lifetime);

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//HintName: RouteBuilder.Dummy_GetUsersQuery.g.cs
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;

#pragma warning disable CS1591

namespace Microsoft.AspNetCore.Builder
{
public static partial class TestsRoutesBuilder
{
private static void MapDummy_GetUsersQueryEndpoint(IEndpointRouteBuilder app)
{
var endpoint = app
.MapGet(
"/test",
[global::Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute<int>(200)]
async (
[AsParameters] global::Dummy.GetUsersQuery.Query parameters,
[FromServices] global::Dummy.GetUsersQuery.Handler handler,
CancellationToken token
) =>
{
var ret = await handler.HandleAsync(parameters, token);
return ret;
}
);

_ = endpoint.AllowAnonymous();
}
}
}

namespace Dummy
{

/// <remarks><see cref="global::Dummy.GetUsersQuery.Query" /> registered using <c>[AsParameters]</c></remarks>
partial class GetUsersQuery;
}
Loading

0 comments on commit 61cee46

Please sign in to comment.