mirror of
https://github.com/AlexMacocian/Net.Sdk.Web.Extensions.git
synced 2026-07-15 13:39:29 +00:00
Setup route map source generator
This commit is contained in:
@@ -24,6 +24,7 @@ jobs:
|
||||
Solution_Path: Net.Sdk.Web.Extensions.sln
|
||||
Source_Project_Path: Net.Sdk.Web.Extensions\Net.Sdk.Web.Extensions.csproj
|
||||
Azure_Project_path: Net.Sdk.Web.Extensions.Azure\Net.Sdk.Web.Extensions.Azure.csproj
|
||||
SourceGenerators_Project_path: Net.Sdk.Web.Extensions.SourceGenerators\Net.Sdk.Web.Extensions.SourceGenerators.csproj
|
||||
Actions_Allow_Unsecure_Commands: true
|
||||
|
||||
steps:
|
||||
@@ -57,5 +58,11 @@ jobs:
|
||||
- name: Package Net.Sdk.Extensions.Azure
|
||||
run: dotnet pack -c Release -o . $env:Azure_Project_Path
|
||||
|
||||
- name: Build Net.Sdk.Extensions.SourceGenerators project
|
||||
run: dotnet build $env:SourceGenerators_Project_path -c $env:Configuration
|
||||
|
||||
- name: Package Net.Sdk.Extensions.SourceGenerators
|
||||
run: dotnet pack -c Release -o . $env:SourceGenerators_Project_path
|
||||
|
||||
- name: Publish
|
||||
run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
@@ -6,7 +6,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Version>0.7</Version>
|
||||
<Version>0.8</Version>
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
@@ -23,11 +23,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Core" Version="1.42.0" />
|
||||
<PackageReference Include="Azure.Data.Tables" Version="12.9.0" />
|
||||
<PackageReference Include="Azure.Core" Version="1.43.0" />
|
||||
<PackageReference Include="Azure.Data.Tables" Version="12.9.1" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.12.0" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.21.2" />
|
||||
<PackageReference Include="Net.Sdk.Web.Extensions" Version="0.7.0" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.22.0" />
|
||||
<PackageReference Include="Net.Sdk.Web.Extensions" Version="0.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Net.Sdk.Web.Extensions.SourceGenerators.Tests;
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
public static void Build()
|
||||
{
|
||||
EndpointRouteBuilder builder = new EndpointRouteBuilder();
|
||||
builder.MapDeleteSimpleRoute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.SourceGenerators.Tests;
|
||||
|
||||
public class EndpointRouteBuilder : IEndpointRouteBuilder
|
||||
{
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
public ICollection<EndpointDataSource> DataSources { get; }
|
||||
|
||||
public IApplicationBuilder CreateApplicationBuilder()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Net.Sdk.Web.Extensions.SourceGenerators.Tests.Models;
|
||||
|
||||
public class SimpleRequest
|
||||
{
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<GenerateMSBuildDebuggingInformation>true</GenerateMSBuildDebuggingInformation>
|
||||
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\Generated</CompilerGeneratedFilesOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
|
||||
<PackageReference Include="Net.Sdk.Web.Extensions.SourceGenerators" Version="0.1.19" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,17 @@
|
||||
using Net.Sdk.Web.Extensions.SourceGenerators.Tests.Models;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.SourceGenerators.Tests.Routes;
|
||||
|
||||
[GenerateMapDelete(Pattern = "simple")]
|
||||
public sealed class SimpleRoute : IRoute<SimpleRequest>
|
||||
{
|
||||
public Task<Response> HandleRequest(SimpleRequest? request, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<SimpleRequest?> PreProcess(HttpContext? context, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
namespace Net.Sdk.Web.Extensions.SourceGenerators;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public const string Partial = "partial";
|
||||
public const string Public = "public";
|
||||
public const string Abstract = "abstract";
|
||||
public const string Sealed = "sealed";
|
||||
public const string Static = "static";
|
||||
public const string StringType = "string";
|
||||
public const string IntType = "int";
|
||||
public const string ObjectType = "object";
|
||||
public const string Default = "default";
|
||||
public const string Namespace = "Net.Sdk.Web";
|
||||
public const string UsingSystemThreading = "System.Threading";
|
||||
public const string UsingMicrosoftAspNetCoreHttp = "Microsoft.AspNetCore.Http";
|
||||
public const string UsingMicrosoftAspNetCoreRouting = "Microsoft.AspNetCore.Routing";
|
||||
public const string UsingMicrosoftAspNetCoreBuilder = "Microsoft.AspNetCore.Builder";
|
||||
|
||||
public const string RouteFileName = "Route";
|
||||
public const string RouteInterface = "IRoute";
|
||||
public const string RouteTypeParameter = "TRequest";
|
||||
public const string PreProcessRequestReturnType = "Task<TRequest?>";
|
||||
public const string PreProcessRequestMethodName = "PreProcess";
|
||||
public const string HandleRequestReturnType = "Task<Response>";
|
||||
public const string HandleRequestMethodName = "HandleRequest";
|
||||
public const string HttpContextTypeName = "HttpContext?";
|
||||
public const string HttpContextParameterName = "context";
|
||||
public const string RequestParameterName = "request";
|
||||
|
||||
public const string ResponseTypeName = "Response";
|
||||
public const string IResultTypeName = "IResult";
|
||||
public const string SuccessResponseTypeName = "SuccessResponse";
|
||||
public const string FailureResponseTypeName = "FailureResponse";
|
||||
public const string ContentResponseTypeName = "ContentResponse";
|
||||
public const string JsonResponseTypeName = "JsonResponse";
|
||||
public const string StatusCodePropertyName = "StatusCode";
|
||||
public const string ErrorMessagePropertyName = "ErrorMessage";
|
||||
public const string ContentStringPropertyName = "ContentString";
|
||||
public const string ValuePropertyName = "Value";
|
||||
public const string ContentTypePropertyName = "ContentType";
|
||||
public const string OkSuccessResponse = "Ok";
|
||||
public const string CreatedSuccessResponse = "Created";
|
||||
public const string ContentContentResponse = "Content";
|
||||
public const string JsonJsonResponse = "Json";
|
||||
public const string PageContentResponse = "Page";
|
||||
public const string BadRequestFailureResponse = "BadRequest";
|
||||
public const string UnauthorizedFailureResponse = "Unauthorized";
|
||||
public const string ForbiddenFailureResponse = "Forbidden";
|
||||
public const string NotFoundFailureResponse = "NotFound";
|
||||
public const string ServerErrorFailureResponse = "ServerError";
|
||||
public const string GetResult = "GetResult";
|
||||
public const string ContentParameterName = "content";
|
||||
public const string ContentTypeParameterName = "contentType";
|
||||
public const string ValueParameterName = "value";
|
||||
public const string ErrorMessageParameterName = "errorMessage";
|
||||
|
||||
public const string CancellationTokenTypeName = "CancellationToken";
|
||||
public const string CancellationTokenParameterName = "cancellationToken";
|
||||
|
||||
public const string GetAttributeName = "GenerateMapGetAttribute";
|
||||
public const string GetAttributeShortName = "GenerateMapGet";
|
||||
public const string PostAttributeName = "GenerateMapPostAttribute";
|
||||
public const string PostAttributeShortName = "GenerateMapPost";
|
||||
public const string PutAttributeName = "GenerateMapPutAttribute";
|
||||
public const string PutAttributeShortName = "GenerateMapPut";
|
||||
public const string DeleteAttributeName = "GenerateMapDeleteAttribute";
|
||||
public const string DeleteAttributeShortName = "GenerateMapDelete";
|
||||
|
||||
public const string PatternPropertyName = "Pattern";
|
||||
|
||||
public const string EndpointRouteBuilderExtensionsName = "EndpointRouteBuilderExtensions";
|
||||
public const string IEndpointRouteBuilderTypeName = "IEndpointRouteBuilder";
|
||||
public const string BuilderParameterName = "builder";
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Sybil;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.SourceGenerators;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
#nullable enable
|
||||
public class MapEndpointGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
|
||||
predicate: static (s, _) => s is ClassDeclarationSyntax,
|
||||
transform: static (ctx, _) => GetFilteredClassDeclarationSyntax(ctx)).Where(static c => c is not null);
|
||||
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
|
||||
context.RegisterSourceOutput(compilationAndClasses, (sourceProductionContext, tuple) => Execute(tuple.Left, tuple.Right, sourceProductionContext));
|
||||
}
|
||||
|
||||
private static ClassDeclarationSyntax? GetFilteredClassDeclarationSyntax(GeneratorSyntaxContext context)
|
||||
{
|
||||
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
|
||||
if (classDeclarationSyntax.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>()
|
||||
.Any(s => s.Name.ToString() == Constants.GetAttributeName || s.Name.ToString() == Constants.GetAttributeShortName ||
|
||||
s.Name.ToString() == Constants.PostAttributeName || s.Name.ToString() == Constants.PostAttributeShortName ||
|
||||
s.Name.ToString() == Constants.PutAttributeName || s.Name.ToString() == Constants.PutAttributeShortName ||
|
||||
s.Name.ToString() == Constants.DeleteAttributeName || s.Name.ToString() == Constants.DeleteAttributeShortName))
|
||||
{
|
||||
return classDeclarationSyntax;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax?> classes, SourceProductionContext sourceProductionContext)
|
||||
{
|
||||
if (classes.IsDefaultOrEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var maybeLanguageVersion = (compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions)?.LanguageVersion;
|
||||
if (!maybeLanguageVersion.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var languageVersion = maybeLanguageVersion.Value;
|
||||
var builder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithUsing(Constants.UsingSystemThreading)
|
||||
.WithUsing(Constants.UsingMicrosoftAspNetCoreRouting)
|
||||
.WithUsing(Constants.UsingMicrosoftAspNetCoreHttp)
|
||||
.WithUsing(Constants.UsingMicrosoftAspNetCoreBuilder);
|
||||
|
||||
var routeUsings = new HashSet<string>();
|
||||
|
||||
var namespaceBuilder = languageVersion >= LanguageVersion.CSharp10 ? SyntaxBuilder.CreateFileScopedNamespace(Constants.Namespace) : SyntaxBuilder.CreateNamespace(Constants.Namespace);
|
||||
builder.WithNamespace(namespaceBuilder);
|
||||
|
||||
var extensionClassBuilder = SyntaxBuilder.CreateClass(Constants.EndpointRouteBuilderExtensionsName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}");
|
||||
namespaceBuilder.WithClass(extensionClassBuilder);
|
||||
|
||||
foreach(var classDeclarationSyntax in classes)
|
||||
{
|
||||
if (classDeclarationSyntax is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetParentOfType<BaseNamespaceDeclarationSyntax>(classDeclarationSyntax) is BaseNamespaceDeclarationSyntax baseNamespaceDeclarationSyntax)
|
||||
{
|
||||
routeUsings.Add(baseNamespaceDeclarationSyntax.Name.ToString());
|
||||
}
|
||||
|
||||
var attributes = classDeclarationSyntax.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>().ToList();
|
||||
|
||||
if (attributes.FirstOrDefault(a => a.Name.ToString() == Constants.GetAttributeName || a.Name.ToString() == Constants.GetAttributeShortName) is AttributeSyntax getAttributeSyntax)
|
||||
{
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.IEndpointRouteBuilderTypeName, $"MapGet{classDeclarationSyntax.Identifier}")
|
||||
.WithThisParameter(Constants.IEndpointRouteBuilderTypeName, Constants.BuilderParameterName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}");
|
||||
|
||||
var pattern = getAttributeSyntax.ArgumentList?.Arguments.OfType<AttributeArgumentSyntax>().FirstOrDefault(a => a.NameEquals?.Name.Identifier.Text == Constants.PatternPropertyName)?.Expression.ToString().Trim('"');
|
||||
if (pattern is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
methodBuilder.WithBody(@$"
|
||||
builder.MapGet(""{pattern}"", async (HttpContext context, {classDeclarationSyntax.Identifier} route) =>
|
||||
{{
|
||||
var request = await route.PreProcess(context, context.RequestAborted);
|
||||
var response = await route.HandleRequest(request, context.RequestAborted);
|
||||
return response.GetResult();
|
||||
}});
|
||||
|
||||
return builder;");
|
||||
extensionClassBuilder.WithMethod(methodBuilder);
|
||||
}
|
||||
else if (attributes.FirstOrDefault(a => a.Name.ToString() == Constants.PostAttributeName || a.Name.ToString() == Constants.PostAttributeShortName) is AttributeSyntax postAttributeSyntax)
|
||||
{
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.IEndpointRouteBuilderTypeName, $"MapPost{classDeclarationSyntax.Identifier}")
|
||||
.WithThisParameter(Constants.IEndpointRouteBuilderTypeName, Constants.BuilderParameterName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}");
|
||||
|
||||
var pattern = postAttributeSyntax.ArgumentList?.Arguments.OfType<AttributeArgumentSyntax>().FirstOrDefault(a => a.NameEquals?.Name.Identifier.Text == Constants.PatternPropertyName)?.Expression.ToString().Trim('"');
|
||||
if (pattern is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
methodBuilder.WithBody(@$"
|
||||
builder.MapPost(""{pattern}"", async (HttpContext context, {classDeclarationSyntax.Identifier} route) =>
|
||||
{{
|
||||
var request = await route.PreProcess(context, context.RequestAborted);
|
||||
var response = await route.HandleRequest(request, context.RequestAborted);
|
||||
return response.GetResult();
|
||||
}});
|
||||
|
||||
return builder;");
|
||||
extensionClassBuilder.WithMethod(methodBuilder);
|
||||
}
|
||||
else if (attributes.FirstOrDefault(a => a.Name.ToString() == Constants.PutAttributeName || a.Name.ToString() == Constants.PutAttributeShortName) is AttributeSyntax putAttributeSyntax)
|
||||
{
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.IEndpointRouteBuilderTypeName, $"MapPut{classDeclarationSyntax.Identifier}")
|
||||
.WithThisParameter(Constants.IEndpointRouteBuilderTypeName, Constants.BuilderParameterName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}");
|
||||
|
||||
var pattern = putAttributeSyntax.ArgumentList?.Arguments.OfType<AttributeArgumentSyntax>().FirstOrDefault(a => a.NameEquals?.Name.Identifier.Text == Constants.PatternPropertyName)?.Expression.ToString().Trim('"');
|
||||
if (pattern is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
methodBuilder.WithBody(@$"
|
||||
builder.MapPut(""{pattern}"", async (HttpContext context, {classDeclarationSyntax.Identifier} route) =>
|
||||
{{
|
||||
var request = await route.PreProcess(context, context.RequestAborted);
|
||||
var response = await route.HandleRequest(request, context.RequestAborted);
|
||||
return response.GetResult();
|
||||
}});
|
||||
|
||||
return builder;");
|
||||
extensionClassBuilder.WithMethod(methodBuilder);
|
||||
}
|
||||
else if (attributes.FirstOrDefault(a => a.Name.ToString() == Constants.DeleteAttributeName || a.Name.ToString() == Constants.DeleteAttributeShortName) is AttributeSyntax deleteAttributeSyntax)
|
||||
{
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.IEndpointRouteBuilderTypeName, $"MapDelete{classDeclarationSyntax.Identifier}")
|
||||
.WithThisParameter(Constants.IEndpointRouteBuilderTypeName, Constants.BuilderParameterName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}");
|
||||
|
||||
var pattern = deleteAttributeSyntax.ArgumentList?.Arguments.OfType<AttributeArgumentSyntax>().FirstOrDefault(a => a.NameEquals?.Name.Identifier.Text == Constants.PatternPropertyName)?.Expression.ToString().Trim('"');
|
||||
if (pattern is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
methodBuilder.WithBody(@$"
|
||||
builder.MapDelete(""{pattern}"", async (HttpContext context, {classDeclarationSyntax.Identifier} route) =>
|
||||
{{
|
||||
var request = await route.PreProcess(context, context.RequestAborted);
|
||||
var response = await route.HandleRequest(request, context.RequestAborted);
|
||||
return response.GetResult();
|
||||
}});
|
||||
|
||||
return builder;");
|
||||
extensionClassBuilder.WithMethod(methodBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
foreach(var classUsing in routeUsings)
|
||||
{
|
||||
builder.WithUsing(classUsing);
|
||||
}
|
||||
|
||||
var fileSource = builder.Build().ToFullString();
|
||||
sourceProductionContext.AddSource($"{Constants.EndpointRouteBuilderExtensionsName}.g", fileSource);
|
||||
}
|
||||
|
||||
private static T? GetParentOfType<T>(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode.Parent is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (syntaxNode.Parent is T parentNode)
|
||||
{
|
||||
return parentNode;
|
||||
}
|
||||
|
||||
return GetParentOfType<T>(syntaxNode.Parent);
|
||||
}
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Version>0.8</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<Description>Source generators extensions for Net.Sdk.Web.</Description>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Sybil" Version="0.8.3" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Package the generator in the analyzer directory of the nuget package -->
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
<None Include="$(OutputPath)\Sybil.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,121 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Sybil;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.SourceGenerators;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
#nullable enable
|
||||
public class ResponseGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(context =>
|
||||
{
|
||||
GenerateResponse(context);
|
||||
});
|
||||
}
|
||||
|
||||
private static void GenerateResponse(IncrementalGeneratorPostInitializationContext context)
|
||||
{
|
||||
var responseBaseClassBuilder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithUsing(Constants.UsingMicrosoftAspNetCoreHttp)
|
||||
.WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.ResponseTypeName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Abstract}")
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.IResultTypeName, Constants.GetResult)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithBody(
|
||||
@"return this switch
|
||||
{
|
||||
SuccessResponse success => Results.StatusCode(success.StatusCode),
|
||||
FailureResponse failure => Results.Problem(
|
||||
detail: string.IsNullOrWhiteSpace(failure.ErrorMessage) is false ? failure.ErrorMessage : default,
|
||||
statusCode: failure.StatusCode),
|
||||
ContentResponse content => Results.Content(content.ContentString, content.ContentType),
|
||||
JsonResponse json => Results.Ok(json.Value),
|
||||
_ => throw new InvalidOperationException($""Unexpected response type {this.GetType().Name}"")
|
||||
};"))
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.SuccessResponseTypeName, Constants.OkSuccessResponse)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}")
|
||||
.WithExpression("new() { StatusCode = 200 };"))
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.SuccessResponseTypeName, Constants.CreatedSuccessResponse)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}")
|
||||
.WithExpression("new() { StatusCode = 201 };"))
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.ContentResponseTypeName, Constants.ContentContentResponse)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}")
|
||||
.WithParameter(Constants.StringType, Constants.ContentParameterName)
|
||||
.WithParameter($"{Constants.StringType}?", Constants.ContentTypeParameterName, Constants.Default)
|
||||
.WithExpression("new() { ContentString = content, ContentType = contentType };"))
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.JsonResponseTypeName, Constants.JsonJsonResponse)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}")
|
||||
.WithParameter(Constants.ObjectType, Constants.ValueParameterName)
|
||||
.WithExpression("new() { Value = value };"))
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.ContentResponseTypeName, Constants.PageContentResponse)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}")
|
||||
.WithParameter(Constants.StringType, Constants.ContentParameterName)
|
||||
.WithExpression(@"new() { ContentString = content, ContentType = ""text/html"" };"))
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.FailureResponseTypeName, Constants.BadRequestFailureResponse)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}")
|
||||
.WithParameter(Constants.StringType, Constants.ErrorMessageParameterName)
|
||||
.WithExpression(@"new() { StatusCode = 400, ErrorMessage = errorMessage };"))
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.FailureResponseTypeName, Constants.UnauthorizedFailureResponse)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}")
|
||||
.WithParameter(Constants.StringType, Constants.ErrorMessageParameterName)
|
||||
.WithExpression(@"new() { StatusCode = 401, ErrorMessage = errorMessage };"))
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.FailureResponseTypeName, Constants.ForbiddenFailureResponse)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}")
|
||||
.WithParameter(Constants.StringType, Constants.ErrorMessageParameterName)
|
||||
.WithExpression(@"new() { StatusCode = 403, ErrorMessage = errorMessage };"))
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.FailureResponseTypeName, Constants.NotFoundFailureResponse)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}")
|
||||
.WithParameter(Constants.StringType, Constants.ErrorMessageParameterName)
|
||||
.WithExpression(@"new() { StatusCode = 404, ErrorMessage = errorMessage };"))
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.FailureResponseTypeName, Constants.ServerErrorFailureResponse)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Static}")
|
||||
.WithParameter(Constants.StringType, Constants.ErrorMessageParameterName)
|
||||
.WithExpression(@"new() { StatusCode = 500, ErrorMessage = errorMessage };")))
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.SuccessResponseTypeName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithBaseClass(Constants.ResponseTypeName)
|
||||
.WithProperty(SyntaxBuilder.CreateProperty(Constants.IntType, Constants.StatusCodePropertyName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter())))
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.FailureResponseTypeName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithBaseClass(Constants.ResponseTypeName)
|
||||
.WithProperty(SyntaxBuilder.CreateProperty(Constants.IntType, Constants.StatusCodePropertyName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter()))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty($"{Constants.StringType}?", Constants.ErrorMessagePropertyName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter())))
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.ContentResponseTypeName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithBaseClass(Constants.ResponseTypeName)
|
||||
.WithProperty(SyntaxBuilder.CreateProperty($"{Constants.StringType}?", Constants.ContentStringPropertyName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter()))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty($"{Constants.StringType}?", Constants.ContentTypePropertyName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter())))
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.JsonResponseTypeName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithBaseClass(Constants.ResponseTypeName)
|
||||
.WithProperty(SyntaxBuilder.CreateProperty($"{Constants.ObjectType}?", Constants.ValuePropertyName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter())))
|
||||
);
|
||||
|
||||
var syntax = responseBaseClassBuilder.Build();
|
||||
var source = syntax.ToFullString();
|
||||
context.AddSource($"Response.g", $"#nullable enable\n{source}\n#nullable disable\n");
|
||||
}
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Sybil;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.SourceGenerators;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
#nullable enable
|
||||
public class RouteAttributeGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(context =>
|
||||
{
|
||||
GenerateMapAttribute(context, Constants.GetAttributeName);
|
||||
GenerateMapAttribute(context, Constants.PostAttributeName);
|
||||
GenerateMapAttribute(context, Constants.PutAttributeName);
|
||||
GenerateMapAttribute(context, Constants.DeleteAttributeName);
|
||||
});
|
||||
}
|
||||
|
||||
private static void GenerateMapAttribute(IncrementalGeneratorPostInitializationContext context, string attributeName)
|
||||
{
|
||||
var builder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(
|
||||
SyntaxBuilder.CreateNamespace(Constants.Namespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(attributeName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(attributeName)
|
||||
.WithModifier(Constants.Public))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Class)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))
|
||||
.WithBaseClass(nameof(Attribute))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty(Constants.StringType, Constants.PatternPropertyName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter()))));
|
||||
|
||||
var syntax = builder.Build();
|
||||
var source = syntax.ToFullString();
|
||||
context.AddSource($"{attributeName}.g", source);
|
||||
}
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Sybil;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.SourceGenerators;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
#nullable enable
|
||||
public class RouteInterfaceGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(context =>
|
||||
{
|
||||
GenerateInterface(context);
|
||||
});
|
||||
}
|
||||
|
||||
private static void GenerateInterface(IncrementalGeneratorPostInitializationContext context)
|
||||
{
|
||||
var builder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace)
|
||||
.WithInterface(SyntaxBuilder.CreateInterface(Constants.RouteInterface)
|
||||
.WithTypeParameter(SyntaxBuilder.CreateTypeParameter(Constants.RouteTypeParameter))
|
||||
.WithTypeParameterConstraint(SyntaxBuilder.CreateTypeParameterConstraint(Constants.RouteTypeParameter)
|
||||
.WithClass())
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.PreProcessRequestReturnType, Constants.PreProcessRequestMethodName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithParameter(Constants.HttpContextTypeName, Constants.HttpContextParameterName)
|
||||
.WithParameter(Constants.CancellationTokenTypeName, Constants.CancellationTokenParameterName)
|
||||
.WithNoBody())
|
||||
.WithMethod(SyntaxBuilder.CreateMethod(Constants.HandleRequestReturnType, Constants.HandleRequestMethodName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithParameter($"{Constants.RouteTypeParameter}?", Constants.RequestParameterName)
|
||||
.WithParameter(Constants.CancellationTokenTypeName, Constants.CancellationTokenParameterName)
|
||||
.WithNoBody())));
|
||||
|
||||
var syntax = builder.Build();
|
||||
var source = syntax.ToFullString();
|
||||
context.AddSource($"{Constants.RouteFileName}.g", source);
|
||||
}
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
@@ -23,7 +23,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
|
||||
.github\workflows\ci.yaml = .github\workflows\ci.yaml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net.Sdk.Web.Extensions.Azure", "Net.Sdk.Web.Extensions.Azure\Net.Sdk.Web.Extensions.Azure.csproj", "{F9D782AF-2E99-497A-B5C2-5DF6FB254EE5}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net.Sdk.Web.Extensions.Azure", "Net.Sdk.Web.Extensions.Azure\Net.Sdk.Web.Extensions.Azure.csproj", "{F9D782AF-2E99-497A-B5C2-5DF6FB254EE5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net.Sdk.Web.Extensions.SourceGenerators", "Net.Sdk.Web.Extensions.SourceGenerators\Net.Sdk.Web.Extensions.SourceGenerators.csproj", "{A76058F7-5073-472E-B6A7-9467E5FB11D0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net.Sdk.Web.Extensions.SourceGenerators.Tests", "Net.Sdk.Web.Extensions.SourceGenerators.Tests\Net.Sdk.Web.Extensions.SourceGenerators.Tests.csproj", "{77F194BA-FB4C-427D-A0D4-0C790937C5C5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -39,6 +43,14 @@ Global
|
||||
{F9D782AF-2E99-497A-B5C2-5DF6FB254EE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F9D782AF-2E99-497A-B5C2-5DF6FB254EE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F9D782AF-2E99-497A-B5C2-5DF6FB254EE5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A76058F7-5073-472E-B6A7-9467E5FB11D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A76058F7-5073-472E-B6A7-9467E5FB11D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A76058F7-5073-472E-B6A7-9467E5FB11D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A76058F7-5073-472E-B6A7-9467E5FB11D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{77F194BA-FB4C-427D-A0D4-0C790937C5C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{77F194BA-FB4C-427D-A0D4-0C790937C5C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{77F194BA-FB4C-427D-A0D4-0C790937C5C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{77F194BA-FB4C-427D-A0D4-0C790937C5C5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Version>0.7.1</Version>
|
||||
<Version>0.8</Version>
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
@@ -25,9 +25,11 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="SystemExtensions.NetCore" Version="1.0.1" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.4" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard.DependencyInjection" Version="1.4.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="SystemExtensions.NetCore" Version="1.6.8" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.8" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard.DependencyInjection" Version="1.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user