Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions GenHTTP.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<Project Path="Engine\Shared\GenHTTP.Engine.Shared.csproj" />
</Folder>
<Folder Name="/Modules/">
<Project Path="Modules/CodeGen/GenHTTP.Modules.CodeGen.csproj" />
<Project Path="Modules/Functional.CodeGen/GenHTTP.Modules.Functional.CodeGen.csproj" />
<Project Path="Modules\ApiBrowsing\GenHTTP.Modules.ApiBrowsing.csproj" />
<Project Path="Modules\Authentication\GenHTTP.Modules.Authentication.csproj" />
<Project Path="Modules\Redirects\GenHTTP.Modules.Redirects.csproj" />
Expand Down
16 changes: 16 additions & 0 deletions Modules/CodeGen/GenHTTP.Modules.CodeGen.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>

<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0</TargetFrameworks>

<IsPackable>false</IsPackable>

</PropertyGroup>

<ItemGroup>

</ItemGroup>

</Project>
36 changes: 36 additions & 0 deletions Modules/Functional.CodeGen/EntryPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using GenHTTP.Modules.Functional.CodeGen.Model;

namespace GenHTTP.Modules.Functional.CodeGen;

public static class EntryPoint
{

public static string Emit(GeneratedSource source)
{
using var template = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("GenHTTP.Modules.Functional.CodeGen.Templates.EntryPoint.scriban");

if (template == null)
{
throw new InvalidOperationException("Entry point template not found in assembly.");
}

using var reader = new StreamReader(template);

var parsed = Scriban.Template.Parse(reader.ReadToEnd());

if (parsed == null)
{
throw new InvalidOperationException("Failed to parse entry point template.");
}

if (parsed.HasErrors)
{
throw new InvalidOperationException($"Failed to parse entry point template: {parsed.Messages}");
}

return parsed.Render(source);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>

<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0</TargetFrameworks>

<IsPackable>false</IsPackable>
<IncludeBuildOutput>false</IncludeBuildOutput>
<OutputItemType>Analyzer</OutputItemType>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>

</PropertyGroup>

<ItemGroup>

<PackageReference Include="Scriban" Version="6.5.2" PrivateAssets="all" />

<ProjectReference Include="..\CodeGen\GenHTTP.Modules.CodeGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="All"/>

<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" PrivateAssets="All"/>

</ItemGroup>

<ItemGroup>
<None Remove="Templates\EntryPoint.scriban" />
<EmbeddedResource Include="Templates\EntryPoint.scriban" />
</ItemGroup>

</Project>
84 changes: 84 additions & 0 deletions Modules/Functional.CodeGen/InlineGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;
using GenHTTP.Modules.Functional.CodeGen.Model;

namespace GenHTTP.Modules.Functional.CodeGen;

[Generator]
public class InlineHandlerGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 1. Compilation provider
var compilationProvider = context.CompilationProvider;

// 2. Syntax provider: suche nur InvocationExpressions
var invocationProvider = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => node is InvocationExpressionSyntax,
transform: static (ctx, _) => TransformIfBuildAs(ctx)
)
.Where(m => m != null);

// 3. Combine Compilation + Matches
var combined = compilationProvider.Combine(invocationProvider.Collect());

// 4. SourceOutput
context.RegisterSourceOutput(combined, (spc, pair) =>
{
var (compilation, matches) = pair;

Check warning on line 31 in Modules/Functional.CodeGen/InlineGenerator.cs

View workflow job for this annotation

GitHub Actions / Test & Coverage

Remove the unused local variable 'compilation'. (https://rules.sonarsource.com/csharp/RSPEC-1481)

Check warning on line 31 in Modules/Functional.CodeGen/InlineGenerator.cs

View workflow job for this annotation

GitHub Actions / Test & Coverage

Remove the unused local variable 'compilation'. (https://rules.sonarsource.com/csharp/RSPEC-1481)
if (matches.IsDefaultOrEmpty) return;

var handlers = new List<GeneratedHandler>();

foreach (var match in matches.OfType<InlineBuildAsMatch>())
{
var identifier = match.Identifier;
if (!string.IsNullOrEmpty(identifier))
{
var typeName = $"GenHTTP_Inline_{Guid.NewGuid():N}";
handlers.Add(new GeneratedHandler(typeName, identifier));
}
}

if (handlers.Count == 0) return;

var source = new GeneratedSource("GenHTTP.Modules.Functional.CodeGen", "10.3.0", handlers);
var code = EntryPoint.Emit(source);
spc.AddSource("GenHTTP.InlineBuilder.Generated.g.cs", SourceText.From(code, Encoding.UTF8));
});
}

private static InlineBuildAsMatch? TransformIfBuildAs(GeneratorSyntaxContext ctx)
{
if (ctx.Node is not InvocationExpressionSyntax invocation) return null;

if (invocation.Expression is not MemberAccessExpressionSyntax ma) return null;
if (ma.Name.Identifier.Text != "BuildAs") return null;

var args = invocation.ArgumentList.Arguments;
if (args.Count != 1) return null;

if (args[0].Expression is not LiteralExpressionSyntax lit) return null;
if (!lit.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.StringLiteralExpression)) return null;

var identifier = lit.Token.ValueText;

return new InlineBuildAsMatch(invocation, identifier);
}
}

// Modellklasse
public class InlineBuildAsMatch
{
public InvocationExpressionSyntax Invocation;

Check warning on line 76 in Modules/Functional.CodeGen/InlineGenerator.cs

View workflow job for this annotation

GitHub Actions / Test & Coverage

Make this field 'private' and encapsulate it in a 'public' property. (https://rules.sonarsource.com/csharp/RSPEC-1104)

Check warning on line 76 in Modules/Functional.CodeGen/InlineGenerator.cs

View workflow job for this annotation

GitHub Actions / Test & Coverage

Make this field 'private' and encapsulate it in a 'public' property. (https://rules.sonarsource.com/csharp/RSPEC-1104)
public string Identifier;

Check warning on line 77 in Modules/Functional.CodeGen/InlineGenerator.cs

View workflow job for this annotation

GitHub Actions / Test & Coverage

Make this field 'private' and encapsulate it in a 'public' property. (https://rules.sonarsource.com/csharp/RSPEC-1104)

Check warning on line 77 in Modules/Functional.CodeGen/InlineGenerator.cs

View workflow job for this annotation

GitHub Actions / Test & Coverage

Make this field 'private' and encapsulate it in a 'public' property. (https://rules.sonarsource.com/csharp/RSPEC-1104)

public InlineBuildAsMatch(InvocationExpressionSyntax invocation, string identifier)
{
Invocation = invocation;
Identifier = identifier;
}
}
16 changes: 16 additions & 0 deletions Modules/Functional.CodeGen/Model/GeneratedHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace GenHTTP.Modules.Functional.CodeGen.Model;

public class GeneratedHandler
{

public string TypeName { get; }

public string Identifier { get; }

public GeneratedHandler(string typeName, string identifier)
{
TypeName = typeName;
Identifier = identifier;
}

}
19 changes: 19 additions & 0 deletions Modules/Functional.CodeGen/Model/GeneratedSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace GenHTTP.Modules.Functional.CodeGen.Model;

public class GeneratedSource
{

public string Generator { get; }

public string GeneratorVersion { get; }

public List<GeneratedHandler> Handlers { get; }

public GeneratedSource(string generator, string generatorVersion, List<GeneratedHandler> handlers)
{
Generator = generator;
GeneratorVersion = generatorVersion;
Handlers = handlers;
}

}
69 changes: 69 additions & 0 deletions Modules/Functional.CodeGen/Models.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace GenHTTP.Modules.Functional.CodeGen;

// Equivalent to InlineFactoryMatch record
internal sealed class InlineFactoryMatch
{
public InvocationExpressionSyntax FactoryCall { get; }

public ITypeSymbol BuilderType { get; }

public InlineFactoryMatch(InvocationExpressionSyntax factoryCall, ITypeSymbol builderType)
{
FactoryCall = factoryCall;
BuilderType = builderType;
}
}

// Equivalent to InlineRoute record
internal sealed class InlineRoute
{
public string Verb { get; }

public string? Path { get; }

public ExpressionSyntax DelegateExpression { get; }

public bool IsSafe { get; }

public string DelegateSource { get; }

public InlineRoute(string verb, string? path, ExpressionSyntax delegateExpression, bool isSafe, string delegateSource)
{
Verb = verb;
Path = path;
DelegateExpression = delegateExpression;
IsSafe = isSafe;
DelegateSource = delegateSource;
}
}

// Equivalent to InlineBuilderGroup record
internal sealed class InlineBuilderGroup
{
public string Key { get; }

public InvocationExpressionSyntax FactoryCall { get; }

public ITypeSymbol BuilderType { get; }

public List<InlineRoute> Routes { get; }

// Primary constructor
public InlineBuilderGroup(string key, InvocationExpressionSyntax factoryCall, ITypeSymbol builderType, List<InlineRoute> routes)
{
Key = key;
FactoryCall = factoryCall;
BuilderType = builderType;
Routes = routes;
}

// Auxiliary constructor to mimic original record constructor
public InlineBuilderGroup(string key, InvocationExpressionSyntax factoryCall, ITypeSymbol builderType, List<InlineRoute> routes, bool dummy)
: this(key, factoryCall, builderType, routes)
{
}

}
34 changes: 34 additions & 0 deletions Modules/Functional.CodeGen/Templates/EntryPoint.scriban
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// <auto-generated />

#nullable enable

using System.CodeDom.Compiler;
using System.Runtime.CompilerServices;

using GenHTTP.Api.Content;
using GenHTTP.Api.Protocol;

using GenHTTP.Modules.Functional.CodeGen;
using GenHTTP.Modules.Reflection;

namespace GenHTTP.Modules.Functional;

{{ for handler in handlers }}
[GeneratedCode("{{ generator }}", "{{ generator_version }}")]
public sealed class {{ handler.type_name }}(MethodRegistry registry) : IHandler
{
[ModuleInitializer]
public static void Register()
{
HandlerRegistry.Add("{{ handler.identifier }}", (registry) => new {{ handler.type_name }}(registry));
}

public ValueTask PrepareAsync() => ValueTask.CompletedTask;

public ValueTask<IResponse?> HandleAsync(IRequest request)
{
var x = registry.Formatting;
return default;
}
}
{{ end }}
29 changes: 29 additions & 0 deletions Modules/Functional/CodeGen/HandlerRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Diagnostics.CodeAnalysis;
using GenHTTP.Api.Content;

using GenHTTP.Modules.Reflection;

namespace GenHTTP.Modules.Functional.CodeGen;

public static class HandlerRegistry
{
private static readonly Dictionary<string, Func<MethodRegistry, IHandler>> Factories = [];

public static void Add(string identifier, Func<MethodRegistry, IHandler> factory)
{
Factories.Add(identifier, factory);
}

public static bool TryGet(string identifier, MethodRegistry registry, [MaybeNullWhen(returnValue: false)] out IHandler handler)
{
if (Factories.TryGetValue(identifier, out var factory))
{
handler = factory(registry);
return true;
}

handler = null;
return false;
}

}
4 changes: 2 additions & 2 deletions Modules/Functional/GenHTTP.Modules.Functional.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
<PackageReadmeFile>README.md</PackageReadmeFile>

</PropertyGroup>

<ItemGroup>

<ProjectReference Include="..\..\API\GenHTTP.Api.csproj" />

<ProjectReference Include="..\Reflection\GenHTTP.Modules.Reflection.csproj" />
<ProjectReference Include="..\Layouting\GenHTTP.Modules.Layouting.csproj" />
<ProjectReference Include="..\Conversion\GenHTTP.Modules.Conversion.csproj" />

</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 3 additions & 1 deletion Modules/Functional/Inline.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GenHTTP.Modules.Functional.Provider;
using System.Runtime.CompilerServices;

Check warning on line 1 in Modules/Functional/Inline.cs

View workflow job for this annotation

GitHub Actions / Test & Coverage

Remove this unnecessary 'using'. (https://rules.sonarsource.com/csharp/RSPEC-1128)

Check warning on line 1 in Modules/Functional/Inline.cs

View workflow job for this annotation

GitHub Actions / Test & Coverage

Remove this unnecessary 'using'. (https://rules.sonarsource.com/csharp/RSPEC-1128)

Check warning on line 1 in Modules/Functional/Inline.cs

View workflow job for this annotation

GitHub Actions / Test & Coverage

Remove this unnecessary 'using'. (https://rules.sonarsource.com/csharp/RSPEC-1128)

Check warning on line 1 in Modules/Functional/Inline.cs

View workflow job for this annotation

GitHub Actions / Test & Coverage

Remove this unnecessary 'using'. (https://rules.sonarsource.com/csharp/RSPEC-1128)
using GenHTTP.Modules.Functional.Provider;

namespace GenHTTP.Modules.Functional;

Expand All @@ -10,4 +11,5 @@
/// which are executed to respond to incoming requests.
/// </summary>
public static InlineBuilder Create() => new();

}
Loading
Loading