Skip to content

Commit cb986dc

Browse files
Add support for compiled service methods (#782)
1 parent ad9eece commit cb986dc

File tree

79 files changed

+2374
-571
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+2374
-571
lines changed

API/Routing/RoutingTarget.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public RoutingTarget(WebPath path)
3636
/// <summary>
3737
/// The segment to be currently handled by the responsible handler.
3838
/// </summary>
39-
public WebPathPart? Current => _index < Path.Parts.Count ? Path.Parts[_index] : null;
39+
public WebPathPart? Current => Next(0);
4040

4141
/// <summary>
4242
/// Specifies, whether the end of the path has been reached.
@@ -65,6 +65,24 @@ public void Advance()
6565

6666
_index++;
6767
}
68+
69+
/// <summary>
70+
/// Acknowledges the number of segments passed as a parameter.
71+
/// </summary>
72+
/// <param name="byOffset">The number of segments to advance by</param>
73+
public void Advance(int byOffset)
74+
{
75+
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(byOffset);
76+
77+
var newIndex = _index + byOffset;
78+
79+
if (newIndex > Path.Parts.Count)
80+
{
81+
throw new InvalidOperationException($"Cannot advance {byOffset} segments from position {_index} with {Path.Parts.Count} segments in total");
82+
}
83+
84+
_index = newIndex;
85+
}
6886

6987
/// <summary>
7088
/// Retrieves the part of the path that still needs to be routed.
@@ -84,6 +102,19 @@ public WebPath GetRemaining()
84102
return new WebPath(resultList, Path.TrailingSlash);
85103
}
86104

105+
/// <summary>
106+
/// Peeks at the next segment identified by the offset index,
107+
/// beginning from the current position.
108+
/// </summary>
109+
/// <param name="offset">The offset to be applied</param>
110+
/// <returns>The segment at the given position or null, if there are no more segments</returns>
111+
public WebPathPart? Next(int offset)
112+
{
113+
var index = _index + offset;
114+
115+
return index < Path.Parts.Count ? Path.Parts[index] : null;
116+
}
117+
87118
#endregion
88119

89120
}

Modules/Controllers/Extensions.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using GenHTTP.Modules.Conversion.Formatters;
55
using GenHTTP.Modules.Conversion.Serializers;
66
using GenHTTP.Modules.Layouting.Provider;
7+
using GenHTTP.Modules.Reflection;
78
using GenHTTP.Modules.Reflection.Injectors;
89

910
namespace GenHTTP.Modules.Controllers;
@@ -21,9 +22,9 @@ public static class Extensions
2122
/// <param name="injectors">Optionally the injectors to be used by this controller</param>
2223
/// <param name="serializers">Optionally the serializers to be used by this controller</param>
2324
/// <param name="formatters">Optionally the formatters to be used by this controller</param>
24-
public static LayoutBuilder AddController<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder builder, string path, IBuilder<InjectionRegistry>? injectors = null, IBuilder<SerializationRegistry>? serializers = null, IBuilder<FormatterRegistry>? formatters = null) where T : new()
25+
public static LayoutBuilder AddController<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder builder, string path, IBuilder<InjectionRegistry>? injectors = null, IBuilder<SerializationRegistry>? serializers = null, IBuilder<FormatterRegistry>? formatters = null, ExecutionMode? mode = null) where T : new()
2526
{
26-
builder.Add(path, Controller.From<T>().Configured(injectors, serializers, formatters));
27+
builder.Add(path, Controller.From<T>().Configured(injectors, serializers, formatters, mode));
2728
return builder;
2829
}
2930

@@ -36,13 +37,13 @@ public static class Extensions
3637
/// <param name="injectors">Optionally the injectors to be used by this controller</param>
3738
/// <param name="serializers">Optionally the serializers to be used by this controller</param>
3839
/// <param name="formatters">Optionally the formatters to be used by this controller</param>
39-
public static LayoutBuilder IndexController<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder builder, IBuilder<InjectionRegistry>? injectors = null, IBuilder<SerializationRegistry>? serializers = null, IBuilder<FormatterRegistry>? formatters = null) where T : new()
40+
public static LayoutBuilder IndexController<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder builder, IBuilder<InjectionRegistry>? injectors = null, IBuilder<SerializationRegistry>? serializers = null, IBuilder<FormatterRegistry>? formatters = null, ExecutionMode? mode = null) where T : new()
4041
{
41-
builder.Add(Controller.From<T>().Configured(injectors, serializers, formatters));
42+
builder.Add(Controller.From<T>().Configured(injectors, serializers, formatters, mode));
4243
return builder;
4344
}
4445

45-
private static ControllerBuilder Configured(this ControllerBuilder builder, IBuilder<InjectionRegistry>? injectors = null, IBuilder<SerializationRegistry>? serializers = null, IBuilder<FormatterRegistry>? formatters = null)
46+
private static ControllerBuilder Configured(this ControllerBuilder builder, IBuilder<InjectionRegistry>? injectors = null, IBuilder<SerializationRegistry>? serializers = null, IBuilder<FormatterRegistry>? formatters = null, ExecutionMode? mode = null)
4647
{
4748
if (injectors != null)
4849
{
@@ -59,6 +60,11 @@ private static ControllerBuilder Configured(this ControllerBuilder builder, IBui
5960
builder.Formatters(formatters);
6061
}
6162

63+
if (mode != null)
64+
{
65+
builder.ExecutionMode(mode.Value);
66+
}
67+
6268
return builder;
6369
}
6470
}

Modules/Controllers/Provider/ControllerBuilder.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
namespace GenHTTP.Modules.Controllers.Provider;
1414

15-
public sealed class ControllerBuilder : IHandlerBuilder<ControllerBuilder>, IRegistryBuilder<ControllerBuilder>
15+
public sealed class ControllerBuilder : IReflectionFrameworkBuilder<ControllerBuilder>
1616
{
1717
private readonly List<IConcernBuilder> _concerns = [];
1818

@@ -26,6 +26,8 @@ public sealed class ControllerBuilder : IHandlerBuilder<ControllerBuilder>, IReg
2626

2727
private IBuilder<SerializationRegistry>? _serializers;
2828

29+
private ExecutionMode? _executionMode;
30+
2931
#region Functionality
3032

3133
public ControllerBuilder Serializers(IBuilder<SerializationRegistry> registry)
@@ -69,6 +71,16 @@ public ControllerBuilder InstanceProvider(Func<IRequest, ValueTask<object>> prov
6971
return this;
7072
}
7173

74+
/// <summary>
75+
/// Sets the execution mode to be used to run functions.
76+
/// </summary>
77+
/// <param name="mode">The mode to be used for execution</param>
78+
public ControllerBuilder ExecutionMode(ExecutionMode mode)
79+
{
80+
_executionMode = mode;
81+
return this;
82+
}
83+
7284
public ControllerBuilder Add(IConcernBuilder concern)
7385
{
7486
_concerns.Add(concern);
@@ -89,7 +101,9 @@ public IHandler Build()
89101

90102
var extensions = new MethodRegistry(serializers, injectors, formatters);
91103

92-
return Concerns.Chain(_concerns, new ControllerHandler(type, instanceProvider, extensions));
104+
var executionSettings = new ExecutionSettings(_executionMode);
105+
106+
return Concerns.Chain(_concerns, new ControllerHandler(type, instanceProvider, executionSettings, extensions));
93107
}
94108

95109
#endregion

Modules/Controllers/Provider/ControllerHandler.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ public sealed partial class ControllerHandler : IHandler, IServiceMethodProvider
1313
{
1414
private static readonly Regex HyphenMatcher = CreateHyphenMatcher();
1515

16-
private MethodCollection? _methods;
17-
1816
#region Get-/Setters
1917

2018
private Type Type { get; }
@@ -23,15 +21,22 @@ public sealed partial class ControllerHandler : IHandler, IServiceMethodProvider
2321

2422
private MethodRegistry Registry { get; }
2523

24+
private ExecutionSettings ExecutionSettings { get; }
25+
26+
public SynchronizedMethodCollection Methods { get; }
27+
2628
#endregion
2729

2830
#region Initialization
2931

30-
public ControllerHandler(Type type, Func<IRequest, ValueTask<object>> instanceProvider, MethodRegistry registry)
32+
public ControllerHandler(Type type, Func<IRequest, ValueTask<object>> instanceProvider, ExecutionSettings executionSettings, MethodRegistry registry)
3133
{
3234
Type = type;
3335
InstanceProvider = instanceProvider;
36+
ExecutionSettings = executionSettings;
3437
Registry = registry;
38+
39+
Methods = new SynchronizedMethodCollection(GetMethodsAsync);
3540
}
3641

3742
#endregion
@@ -40,47 +45,44 @@ public ControllerHandler(Type type, Func<IRequest, ValueTask<object>> instancePr
4045

4146
public ValueTask PrepareAsync() => ValueTask.CompletedTask;
4247

43-
public async ValueTask<IResponse?> HandleAsync(IRequest request) => await (await GetMethodsAsync(request)).HandleAsync(request);
48+
public ValueTask<IResponse?> HandleAsync(IRequest request) => Methods.HandleAsync(request);
4449

45-
public async ValueTask<MethodCollection> GetMethodsAsync(IRequest request)
50+
private async Task<MethodCollection> GetMethodsAsync(IRequest request)
4651
{
47-
if (_methods != null) return _methods;
48-
4952
var found = new List<MethodHandler>();
5053

51-
5254
foreach (var method in Type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
5355
{
5456
var annotation = method.GetCustomAttribute<ControllerActionAttribute>(true) ?? new MethodAttribute();
5557

5658
var arguments = FindPathArguments(method);
5759

58-
var operation = CreateOperation(request, method, arguments, Registry);
60+
var operation = CreateOperation(request, method, ExecutionSettings, annotation, arguments, Registry);
5961

60-
found.Add(new MethodHandler(operation, InstanceProvider, annotation, Registry));
62+
found.Add(new MethodHandler(operation, InstanceProvider, Registry));
6163
}
6264

6365
var result = new MethodCollection(found);
6466

6567
await result.PrepareAsync();
6668

67-
return _methods = result;
69+
return result;
6870
}
6971

70-
private static Operation CreateOperation(IRequest request, MethodInfo method, List<string> arguments, MethodRegistry registry)
72+
private static Operation CreateOperation(IRequest request, MethodInfo method, ExecutionSettings executionSettings, IMethodConfiguration configuration, List<string> arguments, MethodRegistry registry)
7173
{
7274
var pathArguments = string.Join('/', arguments.Select(a => $":{a}"));
7375

7476
if (method.Name == "Index")
7577
{
76-
return OperationBuilder.Create(request, pathArguments.Length > 0 ? $"/{pathArguments}/" : null, method, registry, true);
78+
return OperationBuilder.Create(request, pathArguments.Length > 0 ? $"/{pathArguments}/" : null, method, null, executionSettings, configuration, registry, true);
7779
}
7880

7981
var name = HypenCase(method.Name);
8082

8183
var path = $"/{name}";
8284

83-
return OperationBuilder.Create(request, pathArguments.Length > 0 ? $"{path}/{pathArguments}/" : $"{path}/", method, registry, true);
85+
return OperationBuilder.Create(request, pathArguments.Length > 0 ? $"{path}/{pathArguments}/" : $"{path}/", method, null, executionSettings, configuration, registry, true);
8486
}
8587

8688
private static List<string> FindPathArguments(MethodInfo method)

Modules/Conversion/Formatters/DateOnlyFormatter.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.Text.RegularExpressions;
2+
using GenHTTP.Api.Content;
3+
using GenHTTP.Api.Protocol;
24

35
namespace GenHTTP.Modules.Conversion.Formatters;
46

@@ -21,11 +23,12 @@ public object Read(string value, Type type)
2123
return new DateOnly(year, month, day);
2224
}
2325

24-
throw new ArgumentException($"Input does not match the requested format (yyyy-mm-dd): {value}");
26+
throw new ProviderException(ResponseStatus.BadRequest, $"Input does not match the requested format (yyyy-mm-dd): {value}");
2527
}
2628

2729
public string Write(object value, Type type) => ((DateOnly)value).ToString("yyyy-MM-dd");
2830

2931
[GeneratedRegex(@"^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$", RegexOptions.Compiled)]
3032
private static partial Regex CreateDateOnlyPattern();
33+
3134
}

Modules/Functional/Provider/InlineBuilder.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace GenHTTP.Modules.Functional.Provider;
1212

13-
public class InlineBuilder : IHandlerBuilder<InlineBuilder>, IRegistryBuilder<InlineBuilder>
13+
public class InlineBuilder : IReflectionFrameworkBuilder<InlineBuilder>
1414
{
1515
private static readonly HashSet<FlexibleRequestMethod> AllMethods = [..Enum.GetValues<RequestMethod>().Select(FlexibleRequestMethod.Get)];
1616

@@ -24,6 +24,8 @@ public class InlineBuilder : IHandlerBuilder<InlineBuilder>, IRegistryBuilder<In
2424

2525
private IBuilder<SerializationRegistry>? _serializers;
2626

27+
private ExecutionMode? _executionMode;
28+
2729
#region Functionality
2830

2931
/// <summary>
@@ -57,6 +59,16 @@ public InlineBuilder Formatters(IBuilder<FormatterRegistry> registry)
5759
return this;
5860
}
5961

62+
/// <summary>
63+
/// Sets the execution mode to be used to run functions.
64+
/// </summary>
65+
/// <param name="mode">The mode to be used for execution</param>
66+
public InlineBuilder ExecutionMode(ExecutionMode mode)
67+
{
68+
_executionMode = mode;
69+
return this;
70+
}
71+
6072
/// <summary>
6173
/// Adds a route for a request of any type to the root of the handler.
6274
/// </summary>
@@ -174,7 +186,9 @@ public IHandler Build()
174186

175187
var extensions = new MethodRegistry(serializers, injectors, formatters);
176188

177-
return Concerns.Chain(_concerns, new InlineHandler(_functions, extensions));
189+
var executionSettings = new ExecutionSettings(_executionMode);
190+
191+
return Concerns.Chain(_concerns, new InlineHandler(_functions, extensions, executionSettings));
178192
}
179193

180194
#endregion

Modules/Functional/Provider/InlineHandler.cs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,30 @@
66

77
namespace GenHTTP.Modules.Functional.Provider;
88

9-
public class InlineHandler : IHandler, IServiceMethodProvider
9+
public sealed class InlineHandler : IHandler, IServiceMethodProvider
1010
{
11-
private MethodCollection? _methods;
1211

1312
#region Get-/Setters
1413

1514
private List<InlineFunction> Functions { get; }
1615

1716
private MethodRegistry Registry { get; }
1817

18+
private ExecutionSettings ExecutionSettings { get; }
19+
20+
public SynchronizedMethodCollection Methods { get; }
21+
1922
#endregion
2023

2124
#region Initialization
2225

23-
public InlineHandler(List<InlineFunction> functions, MethodRegistry registry)
26+
public InlineHandler(List<InlineFunction> functions, MethodRegistry registry, ExecutionSettings executionSettings)
2427
{
2528
Functions = functions;
2629
Registry = registry;
30+
ExecutionSettings = executionSettings;
31+
32+
Methods = new SynchronizedMethodCollection(GetMethodsAsync);
2733
}
2834

2935
#endregion
@@ -32,32 +38,30 @@ public InlineHandler(List<InlineFunction> functions, MethodRegistry registry)
3238

3339
public ValueTask PrepareAsync() => ValueTask.CompletedTask;
3440

35-
public async ValueTask<IResponse?> HandleAsync(IRequest request) => await (await GetMethodsAsync(request)).HandleAsync(request);
41+
public ValueTask<IResponse?> HandleAsync(IRequest request) => Methods.HandleAsync(request);
3642

37-
public async ValueTask<MethodCollection> GetMethodsAsync(IRequest request)
43+
private async Task<MethodCollection> GetMethodsAsync(IRequest request)
3844
{
39-
if (_methods != null) return _methods;
40-
4145
var found = new List<MethodHandler>();
4246

4347
foreach (var function in Functions)
4448
{
4549
var method = function.Delegate.Method;
4650

47-
var operation = OperationBuilder.Create(request, function.Path, method, Registry);
51+
var operation = OperationBuilder.Create(request, function.Path, method, function.Delegate, ExecutionSettings, function.Configuration, Registry);
4852

4953
var target = function.Delegate.Target ?? throw new InvalidOperationException("Delegate target must not be null");
5054

5155
var instanceProvider = (IRequest _) => ValueTask.FromResult(target);
5256

53-
found.Add(new MethodHandler(operation, instanceProvider, function.Configuration, Registry));
57+
found.Add(new MethodHandler(operation, instanceProvider, Registry));
5458
}
5559

5660
var result = new MethodCollection(found);
5761

5862
await result.PrepareAsync();
5963

60-
return _methods = result;
64+
return result;
6165
}
6266

6367
#endregion

0 commit comments

Comments
 (0)