aboutsummaryrefslogtreecommitdiffstats
path: root/code
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-12-21 19:14:48 +0100
committerivarlovlie <git@ivarlovlie.no>2022-12-21 19:14:48 +0100
commitdfb5b08f26573799a7254b64e022759ed4acf102 (patch)
tree0829ea32cbaa82921f75c4bbb09f19925a1078ca /code
parent09250198cdd7a7c13bc50664ef3c8b51bdc1ea8b (diff)
downloaddotnet-endpoints-dfb5b08f26573799a7254b64e022759ed4acf102.tar.xz
dotnet-endpoints-dfb5b08f26573799a7254b64e022759ed4acf102.zip
feat: latest, not working
Diffstat (limited to 'code')
-rw-r--r--code/lib/AsyncEndpoint.cs40
-rw-r--r--code/lib/I2R.Endpoints.csproj30
-rw-r--r--code/lib/SyncEndpoint.cs34
-rw-r--r--code/source-generator/EndpointFinder.cs17
-rw-r--r--code/source-generator/EndpointGenerator.cs146
-rw-r--r--code/source-generator/I2R.Endpoints.Generator.csproj18
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>