diff --git a/GenHTTP.slnx b/GenHTTP.slnx
index 97c04037a..cc91cde7a 100644
--- a/GenHTTP.slnx
+++ b/GenHTTP.slnx
@@ -11,6 +11,8 @@
+
+
diff --git a/Modules/CodeGen/GenHTTP.Modules.CodeGen.csproj b/Modules/CodeGen/GenHTTP.Modules.CodeGen.csproj
new file mode 100644
index 000000000..1d4c13c87
--- /dev/null
+++ b/Modules/CodeGen/GenHTTP.Modules.CodeGen.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+ netstandard2.0
+ netstandard2.0
+
+ false
+
+
+
+
+
+
+
+
diff --git a/Modules/Functional.CodeGen/EntryPoint.cs b/Modules/Functional.CodeGen/EntryPoint.cs
new file mode 100644
index 000000000..986d245de
--- /dev/null
+++ b/Modules/Functional.CodeGen/EntryPoint.cs
@@ -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);
+ }
+
+}
diff --git a/Modules/Functional.CodeGen/GenHTTP.Modules.Functional.CodeGen.csproj b/Modules/Functional.CodeGen/GenHTTP.Modules.Functional.CodeGen.csproj
new file mode 100644
index 000000000..2ae8cde73
--- /dev/null
+++ b/Modules/Functional.CodeGen/GenHTTP.Modules.Functional.CodeGen.csproj
@@ -0,0 +1,33 @@
+
+
+
+
+ netstandard2.0
+ netstandard2.0
+
+ false
+ false
+ Analyzer
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Modules/Functional.CodeGen/InlineGenerator.cs b/Modules/Functional.CodeGen/InlineGenerator.cs
new file mode 100644
index 000000000..8a12b6d07
--- /dev/null
+++ b/Modules/Functional.CodeGen/InlineGenerator.cs
@@ -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;
+ if (matches.IsDefaultOrEmpty) return;
+
+ var handlers = new List();
+
+ foreach (var match in matches.OfType())
+ {
+ 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;
+ public string Identifier;
+
+ public InlineBuildAsMatch(InvocationExpressionSyntax invocation, string identifier)
+ {
+ Invocation = invocation;
+ Identifier = identifier;
+ }
+}
diff --git a/Modules/Functional.CodeGen/Model/GeneratedHandler.cs b/Modules/Functional.CodeGen/Model/GeneratedHandler.cs
new file mode 100644
index 000000000..c38ba99e0
--- /dev/null
+++ b/Modules/Functional.CodeGen/Model/GeneratedHandler.cs
@@ -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;
+ }
+
+}
diff --git a/Modules/Functional.CodeGen/Model/GeneratedSource.cs b/Modules/Functional.CodeGen/Model/GeneratedSource.cs
new file mode 100644
index 000000000..312af7a6d
--- /dev/null
+++ b/Modules/Functional.CodeGen/Model/GeneratedSource.cs
@@ -0,0 +1,19 @@
+namespace GenHTTP.Modules.Functional.CodeGen.Model;
+
+public class GeneratedSource
+{
+
+ public string Generator { get; }
+
+ public string GeneratorVersion { get; }
+
+ public List Handlers { get; }
+
+ public GeneratedSource(string generator, string generatorVersion, List handlers)
+ {
+ Generator = generator;
+ GeneratorVersion = generatorVersion;
+ Handlers = handlers;
+ }
+
+}
diff --git a/Modules/Functional.CodeGen/Models.cs b/Modules/Functional.CodeGen/Models.cs
new file mode 100644
index 000000000..b45c0ad48
--- /dev/null
+++ b/Modules/Functional.CodeGen/Models.cs
@@ -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 Routes { get; }
+
+ // Primary constructor
+ public InlineBuilderGroup(string key, InvocationExpressionSyntax factoryCall, ITypeSymbol builderType, List 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 routes, bool dummy)
+ : this(key, factoryCall, builderType, routes)
+ {
+ }
+
+}
diff --git a/Modules/Functional.CodeGen/Templates/EntryPoint.scriban b/Modules/Functional.CodeGen/Templates/EntryPoint.scriban
new file mode 100644
index 000000000..804e0ebf9
--- /dev/null
+++ b/Modules/Functional.CodeGen/Templates/EntryPoint.scriban
@@ -0,0 +1,34 @@
+//
+
+#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 HandleAsync(IRequest request)
+ {
+ var x = registry.Formatting;
+ return default;
+ }
+}
+{{ end }}
\ No newline at end of file
diff --git a/Modules/Functional/CodeGen/HandlerRegistry.cs b/Modules/Functional/CodeGen/HandlerRegistry.cs
new file mode 100644
index 000000000..a854797d9
--- /dev/null
+++ b/Modules/Functional/CodeGen/HandlerRegistry.cs
@@ -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> Factories = [];
+
+ public static void Add(string identifier, Func 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;
+ }
+
+}
diff --git a/Modules/Functional/GenHTTP.Modules.Functional.csproj b/Modules/Functional/GenHTTP.Modules.Functional.csproj
index 1c5b471d8..786fc1faf 100644
--- a/Modules/Functional/GenHTTP.Modules.Functional.csproj
+++ b/Modules/Functional/GenHTTP.Modules.Functional.csproj
@@ -7,7 +7,7 @@
README.md
-
+
@@ -15,7 +15,7 @@
-
+
diff --git a/Modules/Functional/Inline.cs b/Modules/Functional/Inline.cs
index 3b52052c2..900a41c19 100644
--- a/Modules/Functional/Inline.cs
+++ b/Modules/Functional/Inline.cs
@@ -1,4 +1,5 @@
-using GenHTTP.Modules.Functional.Provider;
+using System.Runtime.CompilerServices;
+using GenHTTP.Modules.Functional.Provider;
namespace GenHTTP.Modules.Functional;
@@ -10,4 +11,5 @@ public static class Inline
/// which are executed to respond to incoming requests.
///
public static InlineBuilder Create() => new();
+
}
diff --git a/Modules/Functional/Provider/InlineBuilder.cs b/Modules/Functional/Provider/InlineBuilder.cs
index b2cf9d92b..e14361974 100644
--- a/Modules/Functional/Provider/InlineBuilder.cs
+++ b/Modules/Functional/Provider/InlineBuilder.cs
@@ -5,12 +5,13 @@
using GenHTTP.Modules.Conversion;
using GenHTTP.Modules.Conversion.Formatters;
using GenHTTP.Modules.Conversion.Serializers;
+using GenHTTP.Modules.Functional.CodeGen;
using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.Reflection.Injectors;
namespace GenHTTP.Modules.Functional.Provider;
-public class InlineBuilder : IHandlerBuilder, IRegistryBuilder
+public class InlineBuilder: IHandlerBuilder, IRegistryBuilder
{
private static readonly HashSet AllMethods = [..Enum.GetValues().Select(FlexibleRequestMethod.Get)];
@@ -24,6 +25,8 @@ public class InlineBuilder : IHandlerBuilder, IRegistryBuilder? _serializers;
+ private string? _identifier;
+
#region Functionality
///
@@ -158,6 +161,12 @@ public InlineBuilder On(Delegate function, HashSet? metho
return this;
}
+ public InlineBuilder Identifier(string identifier)
+ {
+ _identifier = identifier;
+ return this;
+ }
+
public InlineBuilder Add(IConcernBuilder concern)
{
_concerns.Add(concern);
@@ -174,9 +183,23 @@ public IHandler Build()
var extensions = new MethodRegistry(serializers, injectors, formatters);
+ if (_identifier != null)
+ {
+ if (HandlerRegistry.TryGet(_identifier, extensions, out var handler))
+ {
+ return Concerns.Chain(_concerns, handler);
+ }
+ }
+
return Concerns.Chain(_concerns, new InlineHandler(_functions, extensions));
}
+ public IHandler BuildAs(string identifier)
+ {
+ Identifier(identifier);
+ return Build();
+ }
+
#endregion
}
diff --git a/Playground/GenHTTP.Playground.csproj b/Playground/GenHTTP.Playground.csproj
index 61342ed8b..6cee440a0 100644
--- a/Playground/GenHTTP.Playground.csproj
+++ b/Playground/GenHTTP.Playground.csproj
@@ -7,6 +7,9 @@
false
+ true
+ $(BaseIntermediateOutputPath)\Generated
+
@@ -47,6 +50,13 @@
+
+
+
+
diff --git a/Playground/Generated.cs b/Playground/Generated.cs
new file mode 100644
index 000000000..5d724c490
--- /dev/null
+++ b/Playground/Generated.cs
@@ -0,0 +1,30 @@
+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;
+
+[GeneratedCode("GenHTTP.Modules.Functional.CodeGen", "10.3.0")]
+public sealed class MyHandler1(MethodRegistry registry) : IHandler
+{
+
+ [ModuleInitializer]
+ public static void Register()
+ {
+ HandlerRegistry.Add("...", (registry) => new MyHandler1(registry));
+ }
+
+ public ValueTask PrepareAsync() => ValueTask.CompletedTask;
+
+ public ValueTask HandleAsync(IRequest request)
+ {
+ var x = registry.Formatting;
+ return default;
+ }
+
+}
diff --git a/Playground/Program.cs b/Playground/Program.cs
index 7fe31c4f0..85d171da6 100644
--- a/Playground/Program.cs
+++ b/Playground/Program.cs
@@ -1,9 +1,10 @@
using GenHTTP.Engine.Internal;
-
-using GenHTTP.Modules.IO;
+using GenHTTP.Modules.Functional;
using GenHTTP.Modules.Practices;
-var content = Content.From(Resource.FromString("Hello World!"));
+var content = Inline.Create()
+ .Get(() => "Hello World!")
+ .BuildAs("identifier");
await Host.Create()
.Handler(content)