diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-12-21 19:14:48 +0100 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-12-21 19:14:48 +0100 |
| commit | dfb5b08f26573799a7254b64e022759ed4acf102 (patch) | |
| tree | 0829ea32cbaa82921f75c4bbb09f19925a1078ca /code | |
| parent | 09250198cdd7a7c13bc50664ef3c8b51bdc1ea8b (diff) | |
| download | dotnet-endpoints-dfb5b08f26573799a7254b64e022759ed4acf102.tar.xz dotnet-endpoints-dfb5b08f26573799a7254b64e022759ed4acf102.zip | |
feat: latest, not working
Diffstat (limited to 'code')
| -rw-r--r-- | code/lib/AsyncEndpoint.cs | 40 | ||||
| -rw-r--r-- | code/lib/I2R.Endpoints.csproj | 30 | ||||
| -rw-r--r-- | code/lib/SyncEndpoint.cs | 34 | ||||
| -rw-r--r-- | code/source-generator/EndpointFinder.cs | 17 | ||||
| -rw-r--r-- | code/source-generator/EndpointGenerator.cs | 146 | ||||
| -rw-r--r-- | code/source-generator/I2R.Endpoints.Generator.csproj | 18 |
6 files changed, 285 insertions, 0 deletions
diff --git a/code/lib/AsyncEndpoint.cs b/code/lib/AsyncEndpoint.cs new file mode 100644 index 0000000..505032c --- /dev/null +++ b/code/lib/AsyncEndpoint.cs @@ -0,0 +1,40 @@ +namespace I2R.Endpoints; + +public static partial class AsyncEndpoint<TBaseEndpoint> +{ + public static class Req<TRequest> + { + public abstract class Res<TResponse> + { + public abstract Task<TResponse> HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + + public abstract class NoRes + { + public abstract Task HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + } + + public static class NoReq + { + public abstract class Res<TResponse> + { + public abstract Task<TResponse> HandleAsync( + CancellationToken cancellationToken = default + ); + } + + public abstract class NoRes + { + public abstract Task HandleAsync( + CancellationToken cancellationToken = default + ); + } + } +}
\ No newline at end of file diff --git a/code/lib/I2R.Endpoints.csproj b/code/lib/I2R.Endpoints.csproj new file mode 100644 index 0000000..90ecd90 --- /dev/null +++ b/code/lib/I2R.Endpoints.csproj @@ -0,0 +1,30 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net7.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <PackageLicenseUrl>https://git.ivar.systems/dotnet-endpoints/tree/COPYING</PackageLicenseUrl> + <RepositoryUrl>https://git.ivar.systems/dotnet-endpoints</RepositoryUrl> + <RepositoryType>git</RepositoryType> + <PackageVersion>1.1.0</PackageVersion> + <Title>I2R.Endpoints</Title> + <Authors>Ivar Løvlie</Authors> + <Description>A library that enables single file endpoints (or whatever).</Description> + <Copyright>Ivar Løvlie</Copyright> + <PackageReleaseNotes> + Initial realease. + </PackageReleaseNotes> + <IsPackable>true</IsPackable> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\source-generator\I2R.Endpoints.Generator.csproj" + OutputItemType="Analyzer" + ReferenceOutputAssembly="false"/> + </ItemGroup> + + <ItemGroup> + <AnalyzerReference Include="..\source-generator\I2R.Endpoints.Generator.csproj"/> + </ItemGroup> + +</Project> diff --git a/code/lib/SyncEndpoint.cs b/code/lib/SyncEndpoint.cs new file mode 100644 index 0000000..f30923a --- /dev/null +++ b/code/lib/SyncEndpoint.cs @@ -0,0 +1,34 @@ +namespace I2R.Endpoints; + +public static partial class SyncEndpoint<TBaseEndpoint> +{ + public static class Req<TRequest> + { + public abstract class Res<TResponse> + { + public abstract TResponse Handle( + TRequest request + ); + } + + public abstract class NoRes + { + public abstract void Handle( + TRequest request + ); + } + } + + public static class NoReq + { + public abstract class Res<TResponse> + { + public abstract TResponse Handle(); + } + + public abstract class NoRes + { + public abstract void Handle(); + } + } +}
\ No newline at end of file diff --git a/code/source-generator/EndpointFinder.cs b/code/source-generator/EndpointFinder.cs new file mode 100644 index 0000000..b30451e --- /dev/null +++ b/code/source-generator/EndpointFinder.cs @@ -0,0 +1,17 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace I2R.Endpoints.Generator; + +public class EndpointFinder : ISyntaxReceiver +{ + public HashSet<ClassDeclarationSyntax> Endpoints { get; } = new(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { + if (syntaxNode is not ClassDeclarationSyntax endpoint) return; + if ((endpoint.BaseList?.Types.Any(c => EndpointGenerator.IsSyncEndpoint(c.ToString())) ?? false) + || (endpoint.BaseList?.Types.Any(c => EndpointGenerator.IsAyncEndpoint(c.ToString())) ?? false)) { + Endpoints.Add(endpoint); + } + } +} diff --git a/code/source-generator/EndpointGenerator.cs b/code/source-generator/EndpointGenerator.cs new file mode 100644 index 0000000..989a1c5 --- /dev/null +++ b/code/source-generator/EndpointGenerator.cs @@ -0,0 +1,146 @@ +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace I2R.Endpoints.Generator; + +[Generator] +public class EndpointGenerator : ISourceGenerator +{ + private const string SourceGenereatedComment = "// Generated by I2R.Endpoints, probably smart to leave it be"; + + public void Initialize(GeneratorInitializationContext context) { + context.RegisterForSyntaxNotifications(() => new EndpointFinder()); + } + + public void Execute(GeneratorExecutionContext context) { + var endpointClasses = ((EndpointFinder) context.SyntaxReceiver)?.Endpoints; + if (endpointClasses == null) { + context.ReportDiagnostic(CreateDebugDiagnostic("no endpoints were found")); + return; + } + + foreach (var info in endpointClasses.Select(GetEndpointInfoFromClass)) { + context.AddSource(info.GeneratedFileName, info.IsSynchronous ? GetSyncSource(info) : GetAsyncSource(info)); + } + } + + private static Diagnostic CreateDebugDiagnostic(string message) { + var descriptor = new DiagnosticDescriptor("debug", "Debug", message, "debug", DiagnosticSeverity.Warning, true); + return Diagnostic.Create(descriptor, null, ""); + } + + private class EndpointInfo + { + public string BaseEndpointClassName { get; set; } + public bool IsSynchronous { get; set; } + public string GeneratedFileName { get; set; } + } + + public static bool IsSyncEndpoint(string name) { + return name.StartsWith("SyncEndpoint"); + } + + public static bool IsAyncEndpoint(string name) { + return name.StartsWith("AsyncEndpoint"); + } + + private static EndpointInfo GetEndpointInfoFromClass(ClassDeclarationSyntax classDeclarationSyntax) { + var baseType = classDeclarationSyntax.BaseList?.Types.FirstOrDefault(); + if (baseType == default) { + return default; + } + + var fullTypeName = baseType.Type.ToString(); + var className = Regex.Match(fullTypeName, "(!?<).+?(?=>)").Value.Replace("<", ""); + return new EndpointInfo() { + BaseEndpointClassName = className, + IsSynchronous = IsSyncEndpoint(baseType.ToString()), + GeneratedFileName = className + ".endpoints.g.cs" + }; + } + + private static string GetSyncSource(EndpointInfo info) { + return $@" +{SourceGenereatedComment} +namespace {typeof(EndpointGenerator).Namespace}; +public static partial class SyncEndpoint<T{info.BaseEndpointClassName}> +{{ + public static class Req<TRequest> + {{ + public abstract class Res<TResponse> : {info.BaseEndpointClassName} + {{ + public abstract TResponse Handle( + TRequest request + ); + }} + + public abstract class NoRes : {info.BaseEndpointClassName} + {{ + public abstract void Handle( + TRequest request + ); + }} + }} + + public static class NoReq + {{ + public abstract class Res<TResponse> : {info.BaseEndpointClassName} + {{ + public abstract TResponse Handle(); + }} + + public abstract class NoRes : {info.BaseEndpointClassName} + {{ + public abstract void Handle(); + }} + }} +}} +"; + } + + private static string GetAsyncSource(EndpointInfo info) { + return $@" +{SourceGenereatedComment} +namespace {typeof(EndpointGenerator).Namespace}; +public static partial class AsyncEndpoint<T> +{{ + public static class Req<TRequest> + {{ + public abstract class Res<TResponse> : {info.BaseEndpointClassName} + {{ + public abstract Task<TResponse> HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + }} + + public abstract class NoRes : {info.BaseEndpointClassName} + {{ + public abstract Task HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + }} + }} + + public static class NoReq + {{ + public abstract class Res<TResponse> : {info.BaseEndpointClassName} + {{ + public abstract Task<TResponse> HandleAsync( + CancellationToken cancellationToken = default + ); + }} + + public abstract class NoRes : {info.BaseEndpointClassName} + {{ + public abstract Task HandleAsync( + CancellationToken cancellationToken = default + ); + }} + }} +}} +"; + } +} diff --git a/code/source-generator/I2R.Endpoints.Generator.csproj b/code/source-generator/I2R.Endpoints.Generator.csproj new file mode 100644 index 0000000..f67f7d3 --- /dev/null +++ b/code/source-generator/I2R.Endpoints.Generator.csproj @@ -0,0 +1,18 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>netstandard2.0</TargetFramework> + <LangVersion>10</LangVersion> + <ImplicitUsings>true</ImplicitUsings> + <DevelopmentDependency>true</DevelopmentDependency> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0"/> + </ItemGroup> + <ItemGroup> + <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/> + </ItemGroup> +</Project> |
