value)
{
- if (path.Length > 0)
+ foreach (var c in value)
{
- if (path[0] != '/')
+ if (c is (< 'A' or > 'Z') and (< 'a' or > 'z') and (< '0' or > '9'))
{
- return $"{path}";
+ return false;
}
}
- return path;
+ return true;
}
#endregion
- #region Regular Expressions
-
- [GeneratedRegex(@"\:([a-z]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")]
- private static partial Regex CreateVarPattern();
-
- [GeneratedRegex(@"\(\?\<([a-z]+)\>([^)]+)\)", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")]
- private static partial Regex CreateRegexPattern();
-
- [GeneratedRegex("^.*", RegexOptions.Compiled)]
- private static partial Regex CreateEmptyWildcardRoute();
-
- [GeneratedRegex("^(/|)$", RegexOptions.Compiled)]
- private static partial Regex CreateEmptyRoute();
-
- #endregion
-
}
diff --git a/Modules/Reflection/Operations/OperationPath.cs b/Modules/Reflection/Operations/OperationPath.cs
deleted file mode 100644
index dcfd96107..000000000
--- a/Modules/Reflection/Operations/OperationPath.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System.Text.RegularExpressions;
-
-namespace GenHTTP.Modules.Reflection.Operations;
-
-public sealed class OperationPath
-{
-
- #region Get-/Setters
-
- ///
- /// An user-friendly string to display this path.
- ///
- public string Name { get; }
-
- ///
- /// The path of the method, converted into a regular
- /// expression to be evaluated at runtime.
- ///
- public Regex Matcher { get; }
-
- ///
- /// True, if this route matches the index of the
- /// scoped segment.
- ///
- public bool IsIndex { get; }
-
- ///
- /// True, if this is a wildcard route that is created
- /// when returning a handler or handler builder from
- /// a method.
- ///
- ///
- /// Wildcard routes have a lower priority compared to
- /// non-wildcard routes and will not be considered
- /// ambiguous.
- ///
- public bool IsWildcard { get; }
-
- #endregion
-
- #region Initialization
-
- public OperationPath(string name, Regex matcher, bool isIndex, bool isWildcard)
- {
- Matcher = matcher;
- Name = name;
- IsIndex = isIndex;
- IsWildcard = isWildcard;
- }
-
- #endregion
-
-}
diff --git a/Modules/Reflection/Operations/SignatureAnalyzer.cs b/Modules/Reflection/Operations/SignatureAnalyzer.cs
index e357bfa43..00e8459e9 100644
--- a/Modules/Reflection/Operations/SignatureAnalyzer.cs
+++ b/Modules/Reflection/Operations/SignatureAnalyzer.cs
@@ -133,7 +133,7 @@ public static OperationResult GetResult(MethodInfo method, MethodRegistry regist
{
return type.IsGenericallyVoid() ? null : type.GenericTypeArguments[0];
}
- if (type == typeof(ValueTask) || type == typeof(Task))
+ if (type.IsAsync())
{
return null;
}
@@ -145,4 +145,5 @@ public static OperationResult GetResult(MethodInfo method, MethodRegistry regist
return type;
}
+
}
diff --git a/Modules/Reflection/Resources/CodeGenerationError.html b/Modules/Reflection/Resources/CodeGenerationError.html
new file mode 100644
index 000000000..3477a1125
--- /dev/null
+++ b/Modules/Reflection/Resources/CodeGenerationError.html
@@ -0,0 +1,22 @@
+
+
+
+
+ Failed to generate handler
+
+
+
+
+
+ Code Generation Failed
+
+ The server failed to compile the code generated for this endpoint. Please submit an error report to our GitHub repository.
+
+ Exception
+ {exception}
+
+ Generated Code
+ {code}
+
+
+
diff --git a/Modules/Reflection/ResponseProvider.cs b/Modules/Reflection/ResponseProvider.cs
index ce084b56c..77bfb7047 100644
--- a/Modules/Reflection/ResponseProvider.cs
+++ b/Modules/Reflection/ResponseProvider.cs
@@ -116,7 +116,6 @@ private static IResponse GetBinaryResponse(IRequest request, object data, Action
private IResponse GetFormattedResponse(IRequest request, object result, Type type, Action? adjustments) => request.Respond()
.Content(Registry.Formatting.Write(result, type) ?? string.Empty)
- .Type(ContentType.TextPlain)
.Adjust(adjustments)
.Build();
diff --git a/Modules/Reflection/Result.cs b/Modules/Reflection/Result.cs
index 567bee73f..8944ce1c3 100644
--- a/Modules/Reflection/Result.cs
+++ b/Modules/Reflection/Result.cs
@@ -123,7 +123,7 @@ public Result Encoding(string encoding)
return this;
}
- void IResultWrapper.Apply(IResponseBuilder builder)
+ public void Apply(IResponseBuilder builder)
{
if (_status != null)
{
diff --git a/Modules/Reflection/Routing/IRoutingSegment.cs b/Modules/Reflection/Routing/IRoutingSegment.cs
new file mode 100644
index 000000000..b7b7b3d30
--- /dev/null
+++ b/Modules/Reflection/Routing/IRoutingSegment.cs
@@ -0,0 +1,28 @@
+using GenHTTP.Api.Routing;
+
+namespace GenHTTP.Modules.Reflection.Routing;
+
+///
+/// A piece of logic that knows how to check the current segment
+/// for a given route and extracts the path arguments from the
+/// incoming request.
+///
+public interface IRoutingSegment
+{
+
+ ///
+ /// The path arguments that are provided by this segment, if any.
+ ///
+ string[] ProvidedArguments { get; }
+
+ ///
+ /// Checks whether the segment is responsible for handling the incoming
+ /// request.
+ ///
+ /// The route of the incoming request
+ /// The offset from the current routing position to be applied
+ /// The sink to write argument values to
+ /// Whether the segment matched and what offset to be applied for this segment
+ (bool matched, int offsetBy) TryMatch(RoutingTarget target, int offset, ref PathArgumentSink argumentSink);
+
+}
diff --git a/Modules/Reflection/Routing/OperationRoute.cs b/Modules/Reflection/Routing/OperationRoute.cs
new file mode 100644
index 000000000..db5bcf7f2
--- /dev/null
+++ b/Modules/Reflection/Routing/OperationRoute.cs
@@ -0,0 +1,29 @@
+namespace GenHTTP.Modules.Reflection.Routing;
+
+public sealed class OperationRoute(string name, IReadOnlyList segments, bool isWildcard)
+{
+
+ ///
+ /// An user-friendly string to display this path.
+ ///
+ public string Name { get; } = name;
+
+ ///
+ /// True, if this is a wildcard route that is created
+ /// when returning a handler or handler builder from
+ /// a method.
+ ///
+ ///
+ /// Wildcard routes have a lower priority compared to
+ /// non-wildcard routes and will not be considered
+ /// ambiguous.
+ ///
+ public bool IsWildcard { get; } = isWildcard;
+
+ ///
+ /// The segments to be evaluated to check whether the route
+ /// has been matched.
+ ///
+ public IReadOnlyList Segments { get; } = segments;
+
+}
diff --git a/Modules/Reflection/Routing/OperationRouter.cs b/Modules/Reflection/Routing/OperationRouter.cs
new file mode 100644
index 000000000..9647a5546
--- /dev/null
+++ b/Modules/Reflection/Routing/OperationRouter.cs
@@ -0,0 +1,39 @@
+using GenHTTP.Api.Routing;
+
+namespace GenHTTP.Modules.Reflection.Routing;
+
+internal static class OperationRouter
+{
+
+ ///
+ /// Checks whether the requested path matches the given route.
+ ///
+ /// The requested path
+ /// The route to check for
+ /// A match if the route is capable of handling the incoming request
+ internal static RoutingMatch? TryMatch(RoutingTarget target, OperationRoute route)
+ {
+ var segments = route.Segments;
+
+ var sink = new PathArgumentSink();
+
+ var offset = 0;
+
+ for (var i = 0; i < segments.Count; i++)
+ {
+ var current = segments[i];
+
+ var (matched, offsetBy) = current.TryMatch(target, i, ref sink);
+
+ if (!matched)
+ {
+ return null;
+ }
+
+ offset += offsetBy;
+ }
+
+ return new(offset, sink.Arguments);
+ }
+
+}
diff --git a/Modules/Reflection/Routing/PathArgumentSink.cs b/Modules/Reflection/Routing/PathArgumentSink.cs
new file mode 100644
index 000000000..b18a96696
--- /dev/null
+++ b/Modules/Reflection/Routing/PathArgumentSink.cs
@@ -0,0 +1,15 @@
+namespace GenHTTP.Modules.Reflection.Routing;
+
+///
+/// Passed as ref struct to routing segments to allow them
+/// to add path argument values during matching.
+///
+public struct PathArgumentSink
+{
+
+ internal Dictionary? Arguments;
+
+ public void Add(string key, string value)
+ => (Arguments ??= new()).Add(key, value);
+
+}
diff --git a/Modules/Reflection/Routing/RoutingMatch.cs b/Modules/Reflection/Routing/RoutingMatch.cs
new file mode 100644
index 000000000..b33b57e67
--- /dev/null
+++ b/Modules/Reflection/Routing/RoutingMatch.cs
@@ -0,0 +1,9 @@
+namespace GenHTTP.Modules.Reflection.Routing;
+
+///
+/// Returned by the operation router if a web service method matched
+/// the incoming request.
+///
+/// The offset to advance the request target by
+/// The arguments read from the request path, if any
+public record RoutingMatch(int Offset, IReadOnlyDictionary? PathArguments);
diff --git a/Modules/Reflection/Routing/Segments/ClosingSegment.cs b/Modules/Reflection/Routing/Segments/ClosingSegment.cs
new file mode 100644
index 000000000..ef91a7583
--- /dev/null
+++ b/Modules/Reflection/Routing/Segments/ClosingSegment.cs
@@ -0,0 +1,40 @@
+using GenHTTP.Api.Routing;
+
+namespace GenHTTP.Modules.Reflection.Routing.Segments;
+
+///
+/// Appended to every operation to check, whether a trailing slash
+/// has been requested and enables wildcards for routes.
+///
+/// If set to true, the segment will only match if a trailing slash has been passed with the request
+/// If set to false, the segment will not match if additional path segments have been passed
+public sealed class ClosingSegment(bool forceTrailingSlash, bool wildcard) : IRoutingSegment
+{
+
+ public string[] ProvidedArguments { get; } = [];
+
+ public (bool matched, int offsetBy) TryMatch(RoutingTarget target, int offset, ref PathArgumentSink argumentSink)
+ {
+ var ended = target.Next(offset) is null;
+
+ if (!ended)
+ {
+ return wildcard ? (true, 0) : (false, 0);
+ }
+
+ var endedWithSlash = target.Path.TrailingSlash;
+
+ if (endedWithSlash)
+ {
+ return (true, 0);
+ }
+
+ if (forceTrailingSlash)
+ {
+ return (false, 0);
+ }
+
+ return (true, offset > 0 ? -1 : 0);
+ }
+
+}
diff --git a/Modules/Reflection/Routing/Segments/RegexSegment.cs b/Modules/Reflection/Routing/Segments/RegexSegment.cs
new file mode 100644
index 000000000..56b1324e7
--- /dev/null
+++ b/Modules/Reflection/Routing/Segments/RegexSegment.cs
@@ -0,0 +1,83 @@
+using System.Text;
+using System.Text.RegularExpressions;
+
+using GenHTTP.Api.Routing;
+
+namespace GenHTTP.Modules.Reflection.Routing.Segments;
+
+///
+/// Checks for multiple variables in one segment or advanced
+/// regex variable definitions.
+///
+internal sealed partial class RegexSegment : IRoutingSegment
+{
+ private static readonly Regex VarPattern = CreateVarPattern();
+
+ private static readonly Regex RegexPattern = CreateRegexPattern();
+
+ private readonly Regex _matcher;
+
+ public string[] ProvidedArguments { get; }
+
+ public RegexSegment(string definition)
+ {
+ var providedArguments = new List();
+
+ var matchBuilder = new StringBuilder(definition);
+
+ // convert parameters of the format ":var" into appropriate groups
+ foreach (Match match in VarPattern.Matches(definition))
+ {
+ var name = match.Groups[1].Value;
+
+ matchBuilder.Replace(match.Value, name.ToParameter());
+
+ providedArguments.Add(name);
+ }
+
+ // convert advanced regex params as well
+ foreach (Match match in RegexPattern.Matches(definition))
+ {
+ var name = match.Groups[1].Value;
+
+ providedArguments.Add(name);
+ }
+
+ _matcher = new Regex($"^{matchBuilder}$", RegexOptions.Compiled);
+
+ ProvidedArguments = providedArguments.ToArray();
+ }
+
+ public (bool matched, int offsetBy) TryMatch(RoutingTarget target, int offset, ref PathArgumentSink argumentSink)
+ {
+ var part = target.Next(offset);
+
+ if (part is null)
+ {
+ return (false, 0);
+ }
+
+ var match = _matcher.Match(part.Value);
+
+ if (!match.Success)
+ {
+ return (false, 0);
+ }
+
+ var groups = match.Groups;
+
+ for (var i = 1; i < groups.Count; i++)
+ {
+ argumentSink.Add(groups[i].Name, groups[i].Value);
+ }
+
+ return (true, 1);
+ }
+
+ [GeneratedRegex(@"\:([a-z]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")]
+ private static partial Regex CreateVarPattern();
+
+ [GeneratedRegex(@"\(\?\<([a-z]+)\>([^)]+)\)", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")]
+ private static partial Regex CreateRegexPattern();
+
+}
diff --git a/Modules/Reflection/Routing/Segments/SimpleVariableSegment.cs b/Modules/Reflection/Routing/Segments/SimpleVariableSegment.cs
new file mode 100644
index 000000000..4b11af734
--- /dev/null
+++ b/Modules/Reflection/Routing/Segments/SimpleVariableSegment.cs
@@ -0,0 +1,31 @@
+using GenHTTP.Api.Routing;
+
+namespace GenHTTP.Modules.Reflection.Routing.Segments;
+
+///
+/// Matches a single variable, such as "/:var/".
+///
+/// The name of the variable to look for (without the colon)
+///
+/// This implementation allows a simple path variable (which is the common use case)
+/// without the need of creating a regex for parsing.
+///
+internal sealed class SimpleVariableSegment(string variableName) : IRoutingSegment
+{
+
+ public string[] ProvidedArguments { get; } = [variableName];
+
+ public (bool matched, int offsetBy) TryMatch(RoutingTarget target, int offset, ref PathArgumentSink argumentSink)
+ {
+ var part = target.Next(offset);
+
+ if (part is null)
+ {
+ return (false, 0);
+ }
+
+ argumentSink.Add(variableName, part.Value);
+ return (true, 1);
+ }
+
+}
diff --git a/Modules/Reflection/Routing/Segments/StringSegment.cs b/Modules/Reflection/Routing/Segments/StringSegment.cs
new file mode 100644
index 000000000..837d6f03c
--- /dev/null
+++ b/Modules/Reflection/Routing/Segments/StringSegment.cs
@@ -0,0 +1,17 @@
+using GenHTTP.Api.Routing;
+
+namespace GenHTTP.Modules.Reflection.Routing.Segments;
+
+///
+/// Matches a single segment within a requested path, such as "/segment/".
+///
+/// The segment to match
+internal sealed class StringSegment(string segment) : IRoutingSegment
+{
+
+ public string[] ProvidedArguments { get; } = [];
+
+ public (bool matched, int offsetBy) TryMatch(RoutingTarget target, int offset, ref PathArgumentSink argumentSink)
+ => (target.Next(offset)?.Value == segment, 1);
+
+}
diff --git a/Modules/Reflection/SynchronizedMethodCollection.cs b/Modules/Reflection/SynchronizedMethodCollection.cs
new file mode 100644
index 000000000..dd877367a
--- /dev/null
+++ b/Modules/Reflection/SynchronizedMethodCollection.cs
@@ -0,0 +1,68 @@
+using GenHTTP.Api.Protocol;
+
+namespace GenHTTP.Modules.Reflection;
+
+///
+/// Ensures that a method collection is initialized exactly once, as this
+/// might be a heavy task involving code generation. Also provides an
+/// execution path that will not generate a state machine.
+///
+public class SynchronizedMethodCollection(Func> factory)
+{
+ private MethodCollection? _instance;
+
+ private Task? _initialization;
+
+ #region State Handling
+
+ ///
+ /// Fetches the method collection to be used.
+ ///
+ /// The currently handled request
+ /// The method collection to be used
+ public Task GetAsync(IRequest request)
+ {
+ if (_instance != null)
+ {
+ return Task.FromResult(_instance);
+ }
+
+ var init = _initialization;
+
+ if (init != null)
+ {
+ return init;
+ }
+
+ init = InitializeAsync(request);
+
+ var original = Interlocked.CompareExchange(ref _initialization, init, null);
+
+ return original ?? init;
+ }
+
+ private async Task InitializeAsync(IRequest request)
+ {
+ var instance = await factory(request).ConfigureAwait(false);
+ _instance = instance;
+ return instance;
+ }
+
+ #endregion
+
+ #region Request Handling
+
+ ///
+ /// Instructs the cached method collection to handle the given request.
+ ///
+ /// The request to be handled
+ /// The response generated by the method collection
+ public ValueTask HandleAsync(IRequest request)
+ => _instance?.HandleAsync(request) ?? HandleWithInitialization(request);
+
+ private async ValueTask HandleWithInitialization(IRequest request)
+ => await (await GetAsync(request)).HandleAsync(request);
+
+ #endregion
+
+}
diff --git a/Modules/Webservices/Extensions.cs b/Modules/Webservices/Extensions.cs
index 3b9b570de..4449f1de4 100644
--- a/Modules/Webservices/Extensions.cs
+++ b/Modules/Webservices/Extensions.cs
@@ -3,6 +3,7 @@
using GenHTTP.Modules.Conversion.Formatters;
using GenHTTP.Modules.Conversion.Serializers;
using GenHTTP.Modules.Layouting.Provider;
+using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.Reflection.Injectors;
using GenHTTP.Modules.Webservices.Provider;
@@ -23,8 +24,8 @@ public static class Extensions
/// Optionally the injectors to be used by this service
/// Optionally the formats to be used by this service
/// Optionally the formatters to be used by this service
- public static LayoutBuilder AddService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder layout, string path, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) where T : new() =>
- layout.Add(path, ServiceResource.From().Configured(injectors, serializers, formatters));
+ public static LayoutBuilder AddService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this LayoutBuilder layout, string path, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null, ExecutionMode? mode = null) where T : new() =>
+ layout.Add(path, ServiceResource.From().Configured(injectors, serializers, formatters, mode));
///
/// Adds the given webservice resource to the layout, accessible using
@@ -35,10 +36,10 @@ public static class Extensions
/// Optionally the injectors to be used by this service
/// Optionally the formats to be used by this service
/// Optionally the formatters to be used by this service
- public static LayoutBuilder AddService(this LayoutBuilder layout, string path, object instance, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null) =>
- layout.Add(path, ServiceResource.From(instance).Configured(injectors, serializers, formatters));
+ public static LayoutBuilder AddService(this LayoutBuilder layout, string path, object instance, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null, ExecutionMode? mode = null) =>
+ layout.Add(path, ServiceResource.From(instance).Configured(injectors, serializers, formatters, mode));
- private static ServiceResourceBuilder Configured(this ServiceResourceBuilder builder, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null)
+ private static ServiceResourceBuilder Configured(this ServiceResourceBuilder builder, IBuilder? injectors = null, IBuilder? serializers = null, IBuilder? formatters = null, ExecutionMode? mode = null)
{
if (injectors != null)
{
@@ -55,6 +56,12 @@ private static ServiceResourceBuilder Configured(this ServiceResourceBuilder bui
builder.Formatters(formatters);
}
+ if (mode != null)
+ {
+ builder.ExecutionMode(mode.Value);
+ }
+
return builder;
}
+
}
diff --git a/Modules/Webservices/Provider/ServiceResourceBuilder.cs b/Modules/Webservices/Provider/ServiceResourceBuilder.cs
index ceac8ed6e..a5730ef23 100644
--- a/Modules/Webservices/Provider/ServiceResourceBuilder.cs
+++ b/Modules/Webservices/Provider/ServiceResourceBuilder.cs
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
-
using GenHTTP.Api.Content;
using GenHTTP.Api.Infrastructure;
using GenHTTP.Api.Protocol;
@@ -7,12 +6,13 @@
using GenHTTP.Modules.Conversion;
using GenHTTP.Modules.Conversion.Formatters;
using GenHTTP.Modules.Conversion.Serializers;
+
using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.Reflection.Injectors;
namespace GenHTTP.Modules.Webservices.Provider;
-public sealed class ServiceResourceBuilder : IHandlerBuilder, IRegistryBuilder
+public sealed class ServiceResourceBuilder : IReflectionFrameworkBuilder
{
private readonly List _concerns = [];
@@ -26,6 +26,8 @@ public sealed class ServiceResourceBuilder : IHandlerBuilder? _serializers;
+ private ExecutionMode? _executionMode;
+
#region Functionality
public ServiceResourceBuilder Type<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : new() => Instance(new T());
@@ -68,6 +70,16 @@ public ServiceResourceBuilder Formatters(IBuilder registry)
return this;
}
+ ///
+ /// Sets the execution mode to be used to run functions.
+ ///
+ /// The mode to be used for execution
+ public ServiceResourceBuilder ExecutionMode(ExecutionMode mode)
+ {
+ _executionMode = mode;
+ return this;
+ }
+
public ServiceResourceBuilder Add(IConcernBuilder concern)
{
_concerns.Add(concern);
@@ -88,7 +100,9 @@ public IHandler Build()
var extensions = new MethodRegistry(serializers, injectors, formatters);
- return Concerns.Chain(_concerns, new ServiceResourceRouter(type, instanceProvider, extensions));
+ var executionSettings = new ExecutionSettings(_executionMode);
+
+ return Concerns.Chain(_concerns, new ServiceResourceRouter(type, instanceProvider, executionSettings, extensions));
}
#endregion
diff --git a/Modules/Webservices/Provider/ServiceResourceRouter.cs b/Modules/Webservices/Provider/ServiceResourceRouter.cs
index b891dba82..7e6959e76 100644
--- a/Modules/Webservices/Provider/ServiceResourceRouter.cs
+++ b/Modules/Webservices/Provider/ServiceResourceRouter.cs
@@ -10,7 +10,6 @@ namespace GenHTTP.Modules.Webservices.Provider;
public sealed class ServiceResourceRouter : IHandler, IServiceMethodProvider
{
- private MethodCollection? _methods;
#region Get-/Setters
@@ -20,15 +19,22 @@ public sealed class ServiceResourceRouter : IHandler, IServiceMethodProvider
private MethodRegistry Registry { get; }
- #endregion
+ private ExecutionSettings ExecutionSettings { get; }
+
+ public SynchronizedMethodCollection Methods { get; }
+
+ #endregion
#region Initialization
- public ServiceResourceRouter(Type type, Func> instanceProvider, MethodRegistry registry)
+ public ServiceResourceRouter(Type type, Func> instanceProvider, ExecutionSettings executionSettings, MethodRegistry registry)
{
Type = type;
InstanceProvider = instanceProvider;
+ ExecutionSettings = executionSettings;
Registry = registry;
+
+ Methods = new SynchronizedMethodCollection(GetMethodsAsync);
}
#endregion
@@ -37,12 +43,10 @@ public ServiceResourceRouter(Type type, Func> instan
public ValueTask PrepareAsync() => ValueTask.CompletedTask;
- public async ValueTask HandleAsync(IRequest request) => await (await GetMethodsAsync(request)).HandleAsync(request);
+ public ValueTask HandleAsync(IRequest request) => Methods.HandleAsync(request);
- public async ValueTask GetMethodsAsync(IRequest request)
+ private async Task GetMethodsAsync(IRequest request)
{
- if (_methods != null) return _methods;
-
var found = new List();
foreach (var method in Type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
@@ -51,9 +55,9 @@ public async ValueTask GetMethodsAsync(IRequest request)
if (attribute is not null)
{
- var operation = OperationBuilder.Create(request, attribute.Path, method, Registry);
+ var operation = OperationBuilder.Create(request, attribute.Path, method, null, ExecutionSettings, attribute, Registry);
- found.Add(new MethodHandler(operation, InstanceProvider, attribute, Registry));
+ found.Add(new MethodHandler(operation, InstanceProvider, Registry));
}
}
@@ -61,7 +65,7 @@ public async ValueTask GetMethodsAsync(IRequest request)
await result.PrepareAsync();
- return _methods = result;
+ return result;
}
#endregion
diff --git a/Playground/GenHTTP.Playground.csproj b/Playground/GenHTTP.Playground.csproj
index 0735ef23d..a267f8ca5 100644
--- a/Playground/GenHTTP.Playground.csproj
+++ b/Playground/GenHTTP.Playground.csproj
@@ -46,6 +46,7 @@
+
diff --git a/Testing/Acceptance/Modules/Controllers/ActionTests.cs b/Testing/Acceptance/Modules/Controllers/ActionTests.cs
index ad90205ed..7898aee0b 100644
--- a/Testing/Acceptance/Modules/Controllers/ActionTests.cs
+++ b/Testing/Acceptance/Modules/Controllers/ActionTests.cs
@@ -1,10 +1,13 @@
using System.Net;
using System.Net.Http.Headers;
+
using GenHTTP.Api.Content;
using GenHTTP.Api.Protocol;
+
using GenHTTP.Modules.Controllers;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Layouting;
+using GenHTTP.Modules.Reflection;
namespace GenHTTP.Testing.Acceptance.Modules.Controllers;
@@ -14,7 +17,7 @@ public sealed class ActionTests
#region Helpers
- private async Task GetRunnerAsync(TestEngine engine) => await TestHost.RunAsync(Layout.Create().AddController("t"), engine: engine);
+ private async Task GetRunnerAsync(TestEngine engine, ExecutionMode mode) => await TestHost.RunAsync(Layout.Create().AddController("t", mode: mode), engine: engine);
#endregion
@@ -53,10 +56,10 @@ public void Void() { }
#region Tests
[TestMethod]
- [MultiEngineTest]
- public async Task TestIndex(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestIndex(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/");
@@ -65,10 +68,10 @@ public async Task TestIndex(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestAction(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestAction(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/action/");
@@ -77,10 +80,10 @@ public async Task TestAction(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestActionWithQuery(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestActionWithQuery(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/action/?query=0815");
@@ -89,10 +92,10 @@ public async Task TestActionWithQuery(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestActionWithQueryFromBody(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestActionWithQueryFromBody(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
var dict = new Dictionary
{
@@ -113,10 +116,10 @@ public async Task TestActionWithQueryFromBody(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestActionWithBody(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestActionWithBody(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
var request = runner.GetRequest("/t/action/");
@@ -132,10 +135,10 @@ public async Task TestActionWithBody(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestActionWithParameter(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestActionWithParameter(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/simple-action/4711/");
@@ -144,10 +147,10 @@ public async Task TestActionWithParameter(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestActionWithBadParameter(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestActionWithBadParameter(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/simple-action/string/");
@@ -155,10 +158,10 @@ public async Task TestActionWithBadParameter(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestActionWithMixedParameters(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestActionWithMixedParameters(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/complex-action/1/2/?three=3");
@@ -167,10 +170,10 @@ public async Task TestActionWithMixedParameters(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestActionWithNoResult(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestActionWithNoResult(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/void/");
@@ -178,10 +181,10 @@ public async Task TestActionWithNoResult(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestNonExistingAction(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestNonExistingAction(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/nope/");
@@ -189,10 +192,10 @@ public async Task TestNonExistingAction(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestHypenCasing(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestHypenCasing(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/hypen-casing-99/");
@@ -201,10 +204,10 @@ public async Task TestHypenCasing(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestIndexController(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestIndexController(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await TestHost.RunAsync(Layout.Create().IndexController(), engine: engine);
+ await using var runner = await TestHost.RunAsync(Layout.Create().IndexController(mode: mode), engine: engine);
using var response = await runner.GetResponseAsync("/simple-action/4711/");
diff --git a/Testing/Acceptance/Modules/Controllers/DataTests.cs b/Testing/Acceptance/Modules/Controllers/DataTests.cs
index 8a4dcc9a4..1c8df2161 100644
--- a/Testing/Acceptance/Modules/Controllers/DataTests.cs
+++ b/Testing/Acceptance/Modules/Controllers/DataTests.cs
@@ -13,12 +13,13 @@ public sealed class DataTests
#region Helpers
- private static async Task GetHostAsync(TestEngine engine)
+ private static async Task GetHostAsync(TestEngine engine, ExecutionMode mode)
{
var app = Layout.Create()
.AddController("t", serializers: Serialization.Default(),
injectors: Injection.Default(),
- formatters: Formatting.Default());
+ formatters: Formatting.Default(),
+ mode: mode);
return await TestHost.RunAsync(app, engine: engine);
}
@@ -39,10 +40,10 @@ public class TestController
#region Tests
[TestMethod]
- [MultiEngineTest]
- public async Task TestDateOnly(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestDateOnly(TestEngine engine, ExecutionMode mode)
{
- await using var host = await GetHostAsync(engine);
+ await using var host = await GetHostAsync(engine, mode);
var request = host.GetRequest("/t/date/", HttpMethod.Post);
@@ -63,10 +64,10 @@ public async Task TestDateOnly(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestInvalidDateOnly(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestInvalidDateOnly(TestEngine engine, ExecutionMode mode)
{
- await using var host = await GetHostAsync(engine);
+ await using var host = await GetHostAsync(engine, mode);
var request = host.GetRequest("/t/date/", HttpMethod.Post);
diff --git a/Testing/Acceptance/Modules/Controllers/IntegrationTests.cs b/Testing/Acceptance/Modules/Controllers/IntegrationTests.cs
index 274a727f8..3422402f5 100644
--- a/Testing/Acceptance/Modules/Controllers/IntegrationTests.cs
+++ b/Testing/Acceptance/Modules/Controllers/IntegrationTests.cs
@@ -1,7 +1,11 @@
using System.Net;
+
using GenHTTP.Api.Protocol;
+
using GenHTTP.Modules.Controllers;
using GenHTTP.Modules.Layouting;
+using GenHTTP.Modules.Reflection;
+
using GenHTTP.Testing.Acceptance.Utilities;
namespace GenHTTP.Testing.Acceptance.Modules.Controllers;
@@ -23,11 +27,14 @@ public class TestController
#endregion
[TestMethod]
- [MultiEngineTest]
- public async Task TestInstance(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestInstance(TestEngine engine, ExecutionMode mode)
{
+ var controller = Controller.From(new TestController())
+ .ExecutionMode(mode);
+
var app = Layout.Create()
- .Add(Controller.From(new TestController()));
+ .Add(controller);
await using var host = await TestHost.RunAsync(app, engine: engine);
diff --git a/Testing/Acceptance/Modules/Controllers/ResultTypeTests.cs b/Testing/Acceptance/Modules/Controllers/ResultTypeTests.cs
index dcab8b8f5..cfd393ccf 100644
--- a/Testing/Acceptance/Modules/Controllers/ResultTypeTests.cs
+++ b/Testing/Acceptance/Modules/Controllers/ResultTypeTests.cs
@@ -15,11 +15,12 @@ public sealed class ResultTypeTests
#region Helpers
- private static async Task GetRunnerAsync(TestEngine engine)
+ private static async Task GetRunnerAsync(TestEngine engine, ExecutionMode mode)
{
var controller = Controller.From()
.Serializers(Serialization.Default())
- .Injectors(Injection.Default());
+ .Injectors(Injection.Default())
+ .ExecutionMode(mode);
return await TestHost.RunAsync(Layout.Create().Add("t", controller), engine: engine);
}
@@ -45,10 +46,10 @@ public sealed class TestController
#region Tests
[TestMethod]
- [MultiEngineTest]
- public async Task ControllerMayReturnHandlerBuilder(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task ControllerMayReturnHandlerBuilder(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/handler-builder/");
@@ -57,10 +58,10 @@ public async Task ControllerMayReturnHandlerBuilder(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task ControllerMayReturnHandler(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task ControllerMayReturnHandler(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/handler/");
@@ -69,10 +70,10 @@ public async Task ControllerMayReturnHandler(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task ControllerMayReturnResponseBuilder(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task ControllerMayReturnResponseBuilder(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/response-builder/");
@@ -81,10 +82,10 @@ public async Task ControllerMayReturnResponseBuilder(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task ControllerMayReturnResponse(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task ControllerMayReturnResponse(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/response/");
diff --git a/Testing/Acceptance/Modules/Controllers/SeoTests.cs b/Testing/Acceptance/Modules/Controllers/SeoTests.cs
index 89eb458b9..7cc0478df 100644
--- a/Testing/Acceptance/Modules/Controllers/SeoTests.cs
+++ b/Testing/Acceptance/Modules/Controllers/SeoTests.cs
@@ -4,6 +4,7 @@
using GenHTTP.Modules.Controllers;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Layouting;
+using GenHTTP.Modules.Reflection;
namespace GenHTTP.Testing.Acceptance.Modules.Controllers;
@@ -18,10 +19,10 @@ public sealed class SeoTests
/// by accepting upper case letters in action names.
///
[TestMethod]
- [MultiEngineTest]
- public async Task TestActionCasingMatters(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestActionCasingMatters(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/Action/");
@@ -32,7 +33,7 @@ public async Task TestActionCasingMatters(TestEngine engine)
#region Helpers
- private async Task GetRunnerAsync(TestEngine engine) => await TestHost.RunAsync(Layout.Create().AddController("t"), engine: engine);
+ private async Task GetRunnerAsync(TestEngine engine, ExecutionMode mode) => await TestHost.RunAsync(Layout.Create().AddController("t", mode: mode), engine: engine);
#endregion
diff --git a/Testing/Acceptance/Modules/Functional/InlineTests.cs b/Testing/Acceptance/Modules/Functional/InlineTests.cs
index 379d158bd..a9dc51d0d 100644
--- a/Testing/Acceptance/Modules/Functional/InlineTests.cs
+++ b/Testing/Acceptance/Modules/Functional/InlineTests.cs
@@ -7,6 +7,7 @@
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Functional;
using GenHTTP.Modules.Redirects;
+using GenHTTP.Modules.Reflection;
namespace GenHTTP.Testing.Acceptance.Modules.Functional;
@@ -15,10 +16,10 @@ public sealed class InlineTests
{
[TestMethod]
- [MultiEngineTest]
- public async Task TestGetRoot(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestGetRoot(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get(() => 42), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get(() => 42).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync();
@@ -26,10 +27,10 @@ public async Task TestGetRoot(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestGetPath(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestGetPath(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get("/blubb", () => 42), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get("/blubb", () => 42).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync("/blubb");
@@ -37,10 +38,10 @@ public async Task TestGetPath(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestGetQueryParam(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestGetQueryParam(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get((int param) => param + 1), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get((int param) => param + 1).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync("/?param=41");
@@ -48,10 +49,10 @@ public async Task TestGetQueryParam(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestGetEmptyBooleanQueryParam(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestGetEmptyBooleanQueryParam(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get((bool param) => param), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get((bool param) => param).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync("/?param=");
@@ -59,10 +60,10 @@ public async Task TestGetEmptyBooleanQueryParam(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestGetEmptyDoubleQueryParam(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestGetEmptyDoubleQueryParam(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get((double param) => param), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get((double param) => param).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync("/?param=");
@@ -70,10 +71,10 @@ public async Task TestGetEmptyDoubleQueryParam(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestGetEmptyStringQueryParam(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestGetEmptyStringQueryParam(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get((string param) => param), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get((string param) => param).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync("/?param=");
@@ -81,10 +82,10 @@ public async Task TestGetEmptyStringQueryParam(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestGetEmptyEnumQueryParam(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestGetEmptyEnumQueryParam(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get((EnumData param) => param), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get((EnumData param) => param).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync("/?param=");
@@ -92,10 +93,10 @@ public async Task TestGetEmptyEnumQueryParam(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestGetPathParam(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestGetPathParam(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get(":param", (int param) => param + 1), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get(":param", (int param) => param + 1).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync("/41");
@@ -103,10 +104,10 @@ public async Task TestGetPathParam(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestNotFound(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestNotFound(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get(() => 42), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get(() => 42).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync("/nope");
@@ -114,15 +115,15 @@ public async Task TestNotFound(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestRaw(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestRaw(TestEngine engine, ExecutionMode mode)
{
await using var host = await TestHost.RunAsync(Inline.Create().Get((IRequest request) =>
{
return request.Respond()
.Status(ResponseStatus.Ok)
.Content("42");
- }), engine: engine);
+ }).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync();
@@ -130,10 +131,10 @@ public async Task TestRaw(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestStream(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestStream(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get(() => new MemoryStream("42"u8.ToArray())), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get(() => new MemoryStream("42"u8.ToArray())).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync();
@@ -141,10 +142,10 @@ public async Task TestStream(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestJson(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestJson(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Get(() => new MyClass("42", 42, 42.0)), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get(() => new MyClass("42", 42, 42.0)).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync();
@@ -152,10 +153,10 @@ public async Task TestJson(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestPostJson(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestPostJson(TestEngine engine, ExecutionMode mode)
{
- await using var host = await TestHost.RunAsync(Inline.Create().Post((MyClass input) => input), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Post((MyClass input) => input).ExecutionMode(mode), engine: engine);
var request = host.GetRequest();
@@ -169,8 +170,8 @@ public async Task TestPostJson(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestAsync(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestAsync(TestEngine engine, ExecutionMode mode)
{
await using var host = await TestHost.RunAsync(Inline.Create().Get(async () =>
{
@@ -181,7 +182,7 @@ public async Task TestAsync(TestEngine engine)
stream.Seek(0, SeekOrigin.Begin);
return stream;
- }), engine: engine);
+ }).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync();
@@ -189,12 +190,12 @@ public async Task TestAsync(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestHandlerBuilder(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestHandlerBuilder(TestEngine engine, ExecutionMode mode)
{
var target = "https://www.google.de/";
- await using var host = await TestHost.RunAsync(Inline.Create().Get(() => Redirect.To(target)), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get(() => Redirect.To(target)).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync();
@@ -202,12 +203,12 @@ public async Task TestHandlerBuilder(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestHandler(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestHandler(TestEngine engine, ExecutionMode mode)
{
var target = "https://www.google.de/";
- await using var host = await TestHost.RunAsync(Inline.Create().Get((IHandler parent) => Redirect.To(target).Build()), engine: engine);
+ await using var host = await TestHost.RunAsync(Inline.Create().Get((IHandler parent) => Redirect.To(target).Build()).ExecutionMode(mode), engine: engine);
using var response = await host.GetResponseAsync();
@@ -218,7 +219,7 @@ public async Task TestHandler(TestEngine engine)
public record MyClass(string String, int Int, double Double);
- private enum EnumData { One, Two }
+ public enum EnumData { One, Two }
#endregion
diff --git a/Testing/Acceptance/Modules/Functional/IntegrationTest.cs b/Testing/Acceptance/Modules/Functional/IntegrationTest.cs
index ef137bcc8..cf256c596 100644
--- a/Testing/Acceptance/Modules/Functional/IntegrationTest.cs
+++ b/Testing/Acceptance/Modules/Functional/IntegrationTest.cs
@@ -1,7 +1,9 @@
using System.Net;
+
using GenHTTP.Modules.Conversion;
using GenHTTP.Modules.Conversion.Formatters;
using GenHTTP.Modules.Functional;
+using GenHTTP.Modules.Reflection;
namespace GenHTTP.Testing.Acceptance.Modules.Functional;
@@ -10,15 +12,16 @@ public class IntegrationTest
{
[TestMethod]
- [MultiEngineTest]
- public async Task TestFormatters(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestFormatters(TestEngine engine, ExecutionMode mode)
{
var formatting = Formatting.Empty()
.Add();
var api = Inline.Create()
.Any("get-bool", (bool value) => value)
- .Formatters(formatting);
+ .Formatters(formatting)
+ .ExecutionMode(mode);
await using var host = await TestHost.RunAsync(api, engine: engine);
diff --git a/Testing/Acceptance/Modules/Functional/MethodTest.cs b/Testing/Acceptance/Modules/Functional/MethodTest.cs
index 036d801d8..f26f80682 100644
--- a/Testing/Acceptance/Modules/Functional/MethodTest.cs
+++ b/Testing/Acceptance/Modules/Functional/MethodTest.cs
@@ -1,6 +1,8 @@
using System.Net;
using System.Net.Http.Headers;
+
using GenHTTP.Modules.Functional;
+using GenHTTP.Modules.Reflection;
namespace GenHTTP.Testing.Acceptance.Modules.Functional;
@@ -9,11 +11,12 @@ public class MethodTest
{
[TestMethod]
- [MultiEngineTest]
- public async Task TestAnyMethod(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestAnyMethod(TestEngine engine, ExecutionMode mode)
{
var app = Inline.Create()
- .Any((List data) => data.Count);
+ .Any((List data) => data.Count)
+ .ExecutionMode(mode);
await using var host = await TestHost.RunAsync(app, engine: engine);
@@ -36,11 +39,12 @@ public async Task TestAnyMethod(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestDelete(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestDelete(TestEngine engine, ExecutionMode mode)
{
var app = Inline.Create()
- .Delete(() => { });
+ .Delete(() => { })
+ .ExecutionMode(mode);
await using var host = await TestHost.RunAsync(app, engine: engine);
@@ -52,11 +56,12 @@ public async Task TestDelete(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestHead(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestHead(TestEngine engine, ExecutionMode mode)
{
var app = Inline.Create()
- .Head(() => "42");
+ .Head(() => "42")
+ .ExecutionMode(mode);
await using var host = await TestHost.RunAsync(app, engine: engine);
diff --git a/Testing/Acceptance/Modules/Reflection/ContentTests.cs b/Testing/Acceptance/Modules/Reflection/ContentTests.cs
index 57d90be8a..991671651 100644
--- a/Testing/Acceptance/Modules/Reflection/ContentTests.cs
+++ b/Testing/Acceptance/Modules/Reflection/ContentTests.cs
@@ -9,13 +9,14 @@ public sealed class ContentTests
{
[TestMethod]
- [MultiEngineTest]
- public async Task TestDeserialization(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestDeserialization(TestEngine engine, ExecutionMode mode)
{
var expectation = new MyType(42);
var handler = Inline.Create()
- .Get(() => expectation);
+ .Get(() => expectation)
+ .ExecutionMode(mode);
await using var host = await TestHost.RunAsync(handler, engine: engine);
@@ -25,11 +26,12 @@ public async Task TestDeserialization(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestNull(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestNull(TestEngine engine, ExecutionMode mode)
{
var handler = Inline.Create()
- .Get(() => (MyType?)null);
+ .Get(() => (MyType?)null)
+ .ExecutionMode(mode);
await using var host = await TestHost.RunAsync(handler, engine: engine);
@@ -39,11 +41,12 @@ public async Task TestNull(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestUnsupported(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestUnsupported(TestEngine engine, ExecutionMode mode)
{
var handler = Inline.Create()
- .Get(() => new Result("Nah").Type(FlexibleContentType.Get("text/html")));
+ .Get(() => new Result("Nah").Type(FlexibleContentType.Get("text/html")))
+ .ExecutionMode(mode);
await using var host = await TestHost.RunAsync(handler, engine: engine);
diff --git a/Testing/Acceptance/Modules/Reflection/ErrorHandlingTests.cs b/Testing/Acceptance/Modules/Reflection/ErrorHandlingTests.cs
index 07645285c..01a6a5dbc 100644
--- a/Testing/Acceptance/Modules/Reflection/ErrorHandlingTests.cs
+++ b/Testing/Acceptance/Modules/Reflection/ErrorHandlingTests.cs
@@ -2,6 +2,7 @@
using GenHTTP.Api.Protocol;
using GenHTTP.Modules.Conversion;
using GenHTTP.Modules.Functional;
+using GenHTTP.Modules.Reflection;
namespace GenHTTP.Testing.Acceptance.Modules.Reflection;
@@ -10,15 +11,16 @@ public class ErrorHandlingTests
{
[TestMethod]
- [MultiEngineTest]
- public async Task TestSerializationNotPossible(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestSerializationNotPossible(TestEngine engine, ExecutionMode mode)
{
var serialization = Serialization.Empty()
.Default(ContentType.AudioMp4);
var api = Inline.Create()
.Get(() => new HashSet())
- .Serializers(serialization);
+ .Serializers(serialization)
+ .ExecutionMode(mode);
await using var host = await TestHost.RunAsync(api, engine: engine);
diff --git a/Testing/Acceptance/Modules/Reflection/InterceptionTests.cs b/Testing/Acceptance/Modules/Reflection/InterceptionTests.cs
index ddd1b5491..e1c3fa684 100644
--- a/Testing/Acceptance/Modules/Reflection/InterceptionTests.cs
+++ b/Testing/Acceptance/Modules/Reflection/InterceptionTests.cs
@@ -60,9 +60,10 @@ public void Configure(object attribute)
#region Tests
[TestMethod]
- public async Task TestInterception()
+ [MultiEngineFrameworkTest]
+ public async Task TestInterception(TestEngine engine, ExecutionMode mode)
{
- var app = Inline.Create().Get([My("intercept")] () => 42);
+ var app = Inline.Create().Get([My("intercept")] (int? q) => 42).ExecutionMode(mode);
await using var host = await TestHost.RunAsync(app);
@@ -74,9 +75,10 @@ public async Task TestInterception()
}
[TestMethod]
- public async Task TestPassThrough()
+ [MultiEngineFrameworkTest]
+ public async Task TestPassThrough(TestEngine engine, ExecutionMode mode)
{
- var app = Inline.Create().Get([My("pass")] () => 42);
+ var app = Inline.Create().Get([My("pass")] () => 42).ExecutionMode(mode);
await using var host = await TestHost.RunAsync(app);
@@ -88,9 +90,10 @@ public async Task TestPassThrough()
}
[TestMethod]
- public async Task TestException()
+ [MultiEngineFrameworkTest]
+ public async Task TestException(TestEngine engine, ExecutionMode mode)
{
- var app = Inline.Create().Get([My("throw")] () => 42);
+ var app = Inline.Create().Get([My("throw")] () => 42).ExecutionMode(mode);
await using var host = await TestHost.RunAsync(app);
diff --git a/Testing/Acceptance/Modules/Reflection/ParameterTests.cs b/Testing/Acceptance/Modules/Reflection/ParameterTests.cs
index c0e395a56..9028ac486 100644
--- a/Testing/Acceptance/Modules/Reflection/ParameterTests.cs
+++ b/Testing/Acceptance/Modules/Reflection/ParameterTests.cs
@@ -26,11 +26,12 @@ public async Task TestCanReadSimpleTypesFromBody()
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestCanPassEmptyString(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestCanPassEmptyString(TestEngine engine, ExecutionMode mode)
{
var inline = Inline.Create()
- .Post(([FromBody] int number) => number);
+ .Post(([FromBody] int number) => number)
+ .ExecutionMode(mode);
await using var runner = await TestHost.RunAsync(inline, engine: engine);
@@ -61,11 +62,12 @@ public async Task TestCanAccessBothBodyAndStream()
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestConversionError(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestConversionError(TestEngine engine, ExecutionMode mode)
{
var inline = Inline.Create()
- .Post(([FromBody] int number) => number);
+ .Post(([FromBody] int number) => number)
+ .ExecutionMode(mode);
await using var runner = await TestHost.RunAsync(inline, engine: engine);
diff --git a/Testing/Acceptance/Modules/Reflection/ResultTests.cs b/Testing/Acceptance/Modules/Reflection/ResultTests.cs
index bb1cd1013..c06e677a8 100644
--- a/Testing/Acceptance/Modules/Reflection/ResultTests.cs
+++ b/Testing/Acceptance/Modules/Reflection/ResultTests.cs
@@ -22,8 +22,8 @@ public record MyPayload(string Message);
#region Tests
[TestMethod]
- [MultiEngineTest]
- public async Task TestResponseCanBeModified(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestResponseCanBeModified(TestEngine engine, ExecutionMode mode)
{
var result = new Result(new MyPayload("Hello World!"))
.Status(ResponseStatus.Accepted)
@@ -37,7 +37,8 @@ public async Task TestResponseCanBeModified(TestEngine engine)
.Encoding("my-encoding");
var inline = Inline.Create()
- .Get(() => result);
+ .Get(() => result)
+ .ExecutionMode(mode);
await using var runner = await TestHost.RunAsync(inline, engine: engine);
@@ -51,13 +52,14 @@ public async Task TestResponseCanBeModified(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestStreamsCanBeWrapped(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestStreamsCanBeWrapped(TestEngine engine, ExecutionMode mode)
{
var stream = new MemoryStream("Hello World!"u8.ToArray());
var inline = Inline.Create()
- .Get(() => new Result(stream).Status(ResponseStatus.Created));
+ .Get(() => new Result(stream).Status(ResponseStatus.Created))
+ .ExecutionMode(mode);
await using var runner = await TestHost.RunAsync(inline, engine: engine);
diff --git a/Testing/Acceptance/Modules/Reflection/RoutingTests.cs b/Testing/Acceptance/Modules/Reflection/RoutingTests.cs
new file mode 100644
index 000000000..a8f7eadfe
--- /dev/null
+++ b/Testing/Acceptance/Modules/Reflection/RoutingTests.cs
@@ -0,0 +1,123 @@
+using GenHTTP.Api.Routing;
+
+using GenHTTP.Modules.Reflection.Routing;
+using GenHTTP.Modules.Reflection.Routing.Segments;
+
+namespace GenHTTP.Testing.Acceptance.Modules.Reflection;
+
+[TestClass]
+public class RoutingTests
+{
+
+ [TestMethod]
+ public void TestSimpleSegment()
+ {
+ var route = CreateRoute(false, new StringSegment("segment"), new ClosingSegment(false, false));
+
+ var target = new RoutingTarget(WebPath.FromString("/segment"));
+
+ var match = OperationRouter.TryMatch(target, route);
+
+ Assert.IsNotNull(match);
+
+ Assert.AreEqual(0, match.Offset);
+ Assert.IsNull(match.PathArguments);
+ }
+
+ [TestMethod]
+ public void TestSimpleSegmentWithTrailingSlash()
+ {
+ var route = CreateRoute(false, new StringSegment("segment"), new ClosingSegment(false, false));
+
+ var target = new RoutingTarget(WebPath.FromString("/segment/"));
+
+ var match = OperationRouter.TryMatch(target, route);
+
+ Assert.IsNotNull(match);
+
+ Assert.AreEqual(1, match.Offset);
+ Assert.IsNull(match.PathArguments);
+ }
+
+ [TestMethod]
+ public void TestMissingSlashFailsRouting()
+ {
+ var route = CreateRoute(false, new StringSegment("segment"), new ClosingSegment(true, false));
+
+ var target = new RoutingTarget(WebPath.FromString("/segment"));
+
+ var match = OperationRouter.TryMatch(target, route);
+
+ Assert.IsNull(match);
+ }
+
+ [TestMethod]
+ public void TestNonWildcardDoesNotAllowAdditionalSegments()
+ {
+ var route = CreateRoute(false, new StringSegment("segment"), new ClosingSegment(false, false));
+
+ var target = new RoutingTarget(WebPath.FromString("/segment/another"));
+
+ var match = OperationRouter.TryMatch(target, route);
+
+ Assert.IsNull(match);
+ }
+
+ [TestMethod]
+ public void TestWildcardAllowsAdditionalSegments()
+ {
+ var route = CreateRoute(true, new StringSegment("segment"), new ClosingSegment(false, true));
+
+ var target = new RoutingTarget(WebPath.FromString("/segment/another"));
+
+ var match = OperationRouter.TryMatch(target, route);
+
+ Assert.IsNotNull(match);
+
+ Assert.AreEqual(1, match.Offset);
+ Assert.IsNull(match.PathArguments);
+ }
+
+ [TestMethod]
+ public void TestSimpleVariable()
+ {
+ var route = CreateRoute(false, new SimpleVariableSegment("var"));
+
+ var target = new RoutingTarget(WebPath.FromString("/99/"));
+
+ var match = OperationRouter.TryMatch(target, route);
+
+ Assert.IsNotNull(match);
+
+ Assert.AreEqual(1, match.Offset);
+
+ Assert.IsNotNull(match.PathArguments);
+ Assert.HasCount(1, match.PathArguments);
+
+ Assert.AreEqual("99", match.PathArguments["var"]);
+ }
+
+ [TestMethod]
+ public void TestComplexVariables()
+ {
+ var route = CreateRoute(false, new RegexSegment("(?[0-9]{2})-(?[0-9]{2})-:three"));
+
+ var target = new RoutingTarget(WebPath.FromString("/99-88-77"));
+
+ var match = OperationRouter.TryMatch(target, route);
+
+ Assert.IsNotNull(match);
+
+ Assert.AreEqual(1, match.Offset);
+
+ Assert.IsNotNull(match.PathArguments);
+ Assert.HasCount(3, match.PathArguments);
+
+ Assert.AreEqual("99", match.PathArguments["one"]);
+ Assert.AreEqual("88", match.PathArguments["two"]);
+ Assert.AreEqual("77", match.PathArguments["three"]);
+ }
+
+ private static OperationRoute CreateRoute(bool wildcard, params IRoutingSegment[] segments) => new("name", segments, wildcard);
+
+}
diff --git a/Testing/Acceptance/Modules/Reflection/WildcardTests.cs b/Testing/Acceptance/Modules/Reflection/WildcardTests.cs
index 688702233..aa92ae311 100644
--- a/Testing/Acceptance/Modules/Reflection/WildcardTests.cs
+++ b/Testing/Acceptance/Modules/Reflection/WildcardTests.cs
@@ -2,6 +2,7 @@
using GenHTTP.Modules.Functional;
using GenHTTP.Modules.IO;
+using GenHTTP.Modules.Reflection;
namespace GenHTTP.Testing.Acceptance.Modules.Reflection;
@@ -10,8 +11,8 @@ public class WildcardTests
{
[TestMethod]
- [MultiEngineTest]
- public async Task TestRouting(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestRouting(TestEngine engine, ExecutionMode mode)
{
var tree = ResourceTree.FromAssembly("Resources");
@@ -22,7 +23,8 @@ public async Task TestRouting(TestEngine engine)
{
Assert.IsGreaterThan(0, tenantId);
return resources;
- });
+ })
+ .ExecutionMode(mode);
await using var host = await TestHost.RunAsync(app);
diff --git a/Testing/Acceptance/Modules/Webservices/AmbiguityTests.cs b/Testing/Acceptance/Modules/Webservices/AmbiguityTests.cs
index f291f8d76..298b3f5dd 100644
--- a/Testing/Acceptance/Modules/Webservices/AmbiguityTests.cs
+++ b/Testing/Acceptance/Modules/Webservices/AmbiguityTests.cs
@@ -2,6 +2,7 @@
using GenHTTP.Api.Content;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Layouting;
+using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.Webservices;
namespace GenHTTP.Testing.Acceptance.Modules.Webservices;
@@ -13,11 +14,11 @@ public sealed class AmbiguityTests
#region Tests
[TestMethod]
- [MultiEngineTest]
- public async Task TestSpecificPreferred(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestSpecificPreferred(TestEngine engine, ExecutionMode mode)
{
var app = Layout.Create()
- .AddService("c");
+ .AddService("c", mode: mode);
await using var host = await TestHost.RunAsync(app, engine: engine);
diff --git a/Testing/Acceptance/Modules/Webservices/ExtensionTests.cs b/Testing/Acceptance/Modules/Webservices/ExtensionTests.cs
index 69150602b..759c0eb45 100644
--- a/Testing/Acceptance/Modules/Webservices/ExtensionTests.cs
+++ b/Testing/Acceptance/Modules/Webservices/ExtensionTests.cs
@@ -13,16 +13,16 @@ public sealed class ExtensionTests
#region Tests
[TestMethod]
- [MultiEngineTest]
- public async Task TestConfiguration(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestConfiguration(TestEngine engine, ExecutionMode mode)
{
var injectors = Injection.Default();
var formats = Serialization.Default();
var app = Layout.Create()
- .AddService("by-type", injectors, formats)
- .AddService("by-instance", new TestService(), injectors, formats);
+ .AddService("by-type", injectors, formats, mode: mode)
+ .AddService("by-instance", new TestService(), injectors, formats, mode: mode);
await using var host = await TestHost.RunAsync(app, engine: engine);
diff --git a/Testing/Acceptance/Modules/Webservices/HandlerResultTests.cs b/Testing/Acceptance/Modules/Webservices/HandlerResultTests.cs
index cf4c9680d..23d8d4f3b 100644
--- a/Testing/Acceptance/Modules/Webservices/HandlerResultTests.cs
+++ b/Testing/Acceptance/Modules/Webservices/HandlerResultTests.cs
@@ -4,6 +4,7 @@
using GenHTTP.Api.Infrastructure;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Layouting;
+using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.StaticWebsites;
using GenHTTP.Modules.Webservices;
@@ -71,11 +72,11 @@ public Task Pathed(string pathParam)
#region Tests
[TestMethod]
- [MultiEngineTest]
- public async Task TestRoot(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestRoot(TestEngine engine, ExecutionMode mode)
{
var app = Layout.Create()
- .AddService("c");
+ .AddService("c", mode: mode);
await using var host = await TestHost.RunAsync(app, engine: engine);
@@ -87,11 +88,11 @@ public async Task TestRoot(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestPathed(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestPathed(TestEngine engine, ExecutionMode mode)
{
var app = Layout.Create()
- .AddService("c");
+ .AddService("c", mode: mode);
await using var host = await TestHost.RunAsync(app, engine: engine);
@@ -103,11 +104,11 @@ public async Task TestPathed(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestPathedAsync(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestPathedAsync(TestEngine engine, ExecutionMode mode)
{
var app = Layout.Create()
- .AddService("c");
+ .AddService("c", mode: mode);
await using var host = await TestHost.RunAsync(app, engine: engine);
diff --git a/Testing/Acceptance/Modules/Webservices/ResultTypeTests.cs b/Testing/Acceptance/Modules/Webservices/ResultTypeTests.cs
index 1be89fda7..4f5e434be 100644
--- a/Testing/Acceptance/Modules/Webservices/ResultTypeTests.cs
+++ b/Testing/Acceptance/Modules/Webservices/ResultTypeTests.cs
@@ -32,19 +32,20 @@ public class ResultTypeTests
#region Helpers
- private async Task GetRunnerAsync(TestEngine engine) => await TestHost.RunAsync(Layout.Create().AddService("t", serializers: Serialization.Default(),
+ private async Task GetRunnerAsync(TestEngine engine, ExecutionMode mode) => await TestHost.RunAsync(Layout.Create().AddService("t", serializers: Serialization.Default(),
injectors: Injection.Default(),
- formatters: Formatting.Default()), engine: engine);
+ formatters: Formatting.Default(),
+ mode: mode), engine: engine);
#endregion
#region Tests
[TestMethod]
- [MultiEngineTest]
- public async Task ControllerMayReturnTask(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task ControllerMayReturnTask(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/task");
@@ -52,10 +53,10 @@ public async Task ControllerMayReturnTask(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task ControllerMayReturnValueTask(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task ControllerMayReturnValueTask(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/value-task");
@@ -63,10 +64,10 @@ public async Task ControllerMayReturnValueTask(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task ControllerMayReturnGenericTask(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task ControllerMayReturnGenericTask(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/generic-task");
@@ -75,10 +76,10 @@ public async Task ControllerMayReturnGenericTask(TestEngine engine)
}
[TestMethod]
- [MultiEngineTest]
- public async Task ControllerMayReturnGenericValueTask(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task ControllerMayReturnGenericValueTask(TestEngine engine, ExecutionMode mode)
{
- await using var runner = await GetRunnerAsync(engine);
+ await using var runner = await GetRunnerAsync(engine, mode);
using var response = await runner.GetResponseAsync("/t/generic-value-task");
diff --git a/Testing/Acceptance/Modules/Webservices/WebserviceTests.cs b/Testing/Acceptance/Modules/Webservices/WebserviceTests.cs
index 150c611db..58993a9f2 100644
--- a/Testing/Acceptance/Modules/Webservices/WebserviceTests.cs
+++ b/Testing/Acceptance/Modules/Webservices/WebserviceTests.cs
@@ -2,18 +2,14 @@
using System.Net.Http.Headers;
using System.Text;
using System.Xml.Serialization;
-
using GenHTTP.Api.Content;
using GenHTTP.Api.Protocol;
-
using GenHTTP.Modules.Conversion;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Layouting;
using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.Webservices;
-
using GenHTTP.Testing.Acceptance.Utilities;
-
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
@@ -103,133 +99,129 @@ public void Empty() { }
#region Tests
[TestMethod]
- [MultiEngineTest]
- public async Task TestEmpty(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestEmpty(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "", async r => { await r.AssertStatusAsync(HttpStatusCode.NoContent); });
+ await WithResponse(engine, mode, "", async r => { await r.AssertStatusAsync(HttpStatusCode.NoContent); });
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestVoidReturn(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestVoidReturn(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "nothing", async r => { await r.AssertStatusAsync(HttpStatusCode.NoContent); });
+ await WithResponse(engine, mode, "nothing", async r => { await r.AssertStatusAsync(HttpStatusCode.NoContent); });
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestPrimitives(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestPrimitives(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "primitive?input=42", async r => Assert.AreEqual("42", await r.GetContentAsync()));
+ await WithResponse(engine, mode, "primitive?input=42", async r => Assert.AreEqual("42", await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestEnums(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestEnums(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "enum?input=One", async r => Assert.AreEqual("One", await r.GetContentAsync()));
+ await WithResponse(engine, mode, "enum?input=One", async r => Assert.AreEqual("One", await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestNullableSet(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestNullableSet(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "nullable?input=1", async r => Assert.AreEqual("1", await r.GetContentAsync()));
+ await WithResponse(engine, mode, "nullable?input=1", async r => Assert.AreEqual("1", await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestNullableNotSet(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestNullableNotSet(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "nullable", async r => { await r.AssertStatusAsync(HttpStatusCode.NoContent); });
+ await WithResponse(engine, mode, "nullable", async r => { await r.AssertStatusAsync(HttpStatusCode.NoContent); });
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestGuid(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestGuid(TestEngine engine, ExecutionMode mode)
{
var id = Guid.NewGuid().ToString();
- await WithResponse(engine, $"guid?id={id}", async r => Assert.AreEqual(id, await r.GetContentAsync()));
+ await WithResponse(engine, mode, $"guid?id={id}", async r => Assert.AreEqual(id, await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestParam(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestParam(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "param/42", async r => Assert.AreEqual("42", await r.GetContentAsync()));
+ await WithResponse(engine, mode, "param/42", async r => Assert.AreEqual("42", await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestConversionFailure(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestConversionFailure(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "param/abc", async r => { await r.AssertStatusAsync(HttpStatusCode.BadRequest); });
+ await WithResponse(engine, mode, "param/abc", async r => { await r.AssertStatusAsync(HttpStatusCode.BadRequest); });
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestRegex(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestRegex(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "regex/42", async r => Assert.AreEqual("42", await r.GetContentAsync()));
+ await WithResponse(engine, mode, "regex/42", async r => Assert.AreEqual("42", await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestEntityWithNulls(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestEntityWithNulls(TestEngine engine, ExecutionMode mode)
{
const string entity = "{\"id\":42}";
- await WithResponse(engine, "entity", HttpMethod.Post, entity, null, null, async r => Assert.AreEqual(entity, await r.GetContentAsync()));
+ await WithResponse(engine, mode, "entity", HttpMethod.Post, entity, null, null, async r => Assert.AreEqual(entity, await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestEntityWithNoNulls(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestEntityWithNoNulls(TestEngine engine, ExecutionMode mode)
{
const string entity = "{\"id\":42,\"nullable\":123.456}";
- await WithResponse(engine, "entity", HttpMethod.Post, entity, null, null, async r => Assert.AreEqual(entity, await r.GetContentAsync()));
+ await WithResponse(engine, mode, "entity", HttpMethod.Post, entity, null, null, async r => Assert.AreEqual(entity, await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestNotSupportedUpload(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestNotSupportedUpload(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "entity", HttpMethod.Post, "123", "bla/blubb", null, async r => { await r.AssertStatusAsync(HttpStatusCode.UnsupportedMediaType); });
+ await WithResponse(engine, mode, "entity", HttpMethod.Post, "123", "bla/blubb", null, async r => { await r.AssertStatusAsync(HttpStatusCode.UnsupportedMediaType); });
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestUnsupportedDownloadEnforcesDefault(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestUnsupportedDownloadEnforcesDefault(TestEngine engine, ExecutionMode mode)
{
const string entity = "{\"id\":42,\"nullable\":123.456}";
- await WithResponse(engine, "entity", HttpMethod.Post, entity, null, "bla/blubb", async r => Assert.AreEqual(entity, await r.GetContentAsync()));
- }
-
- [TestMethod]
- [MultiEngineTest]
- public async Task TestWrongMethod(TestEngine engine)
- {
- await WithResponse(engine, "entity", HttpMethod.Put, "123", null, null, async r => { await r.AssertStatusAsync(HttpStatusCode.MethodNotAllowed); Assert.AreEqual("POST", r.GetContentHeader("Allow")); });
+ await WithResponse(engine, mode, "entity", HttpMethod.Post, entity, null, "bla/blubb", async r => Assert.AreEqual(entity, await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestNoMethod(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestNoMethod(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "idonotexist", async r => { await r.AssertStatusAsync(HttpStatusCode.NotFound); });
+ await WithResponse(engine, mode, "idonotexist", async r => { await r.AssertStatusAsync(HttpStatusCode.NotFound); });
}
[TestMethod]
public async Task TestStream()
{
- await WithResponse(TestEngine.Internal, "stream", HttpMethod.Put, "123456", null, null, async r => Assert.AreEqual("6", await r.GetContentAsync()));
+ foreach (var mode in new[] { ExecutionMode.Reflection, ExecutionMode.Auto })
+ {
+ await WithResponse(TestEngine.Internal, mode, "stream", HttpMethod.Put, "123456", null, null, async r => Assert.AreEqual("6", await r.GetContentAsync()));
+ }
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestByteArrayReturn(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestByteArrayReturn(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "bytes", async r =>
+ await WithResponse(engine, mode, "bytes", async r =>
{
await r.AssertStatusAsync(HttpStatusCode.OK);
Assert.AreEqual("Hello Bytes", await r.GetContentAsync());
@@ -237,10 +229,10 @@ await WithResponse(engine, "bytes", async r =>
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestReadOnlyMemoryReturn(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestReadOnlyMemoryReturn(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "memory", async r =>
+ await WithResponse(engine, mode, "memory", async r =>
{
await r.AssertStatusAsync(HttpStatusCode.OK);
Assert.AreEqual("Hello Memory", await r.GetContentAsync());
@@ -248,26 +240,26 @@ await WithResponse(engine, "memory", async r =>
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestRequestResponse(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestRequestResponse(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "requestResponse", async r => Assert.AreEqual("Hello World", await r.GetContentAsync()));
+ await WithResponse(engine, mode, "requestResponse", async r => Assert.AreEqual("Hello World", await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestRouting(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestRouting(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "request", async r => Assert.AreEqual("yes", await r.GetContentAsync()));
+ await WithResponse(engine, mode, "request", async r => Assert.AreEqual("yes", await r.GetContentAsync()));
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestEntityAsXml(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestEntityAsXml(TestEngine engine, ExecutionMode mode)
{
const string entity = "11234.56";
- await WithResponse(engine, "entity", HttpMethod.Post, entity, "text/xml", "text/xml", async r =>
+ await WithResponse(engine, mode, "entity", HttpMethod.Post, entity, "text/xml", "text/xml", async r =>
{
var result = new XmlSerializer(typeof(TestEntity)).Deserialize(await r.Content.ReadAsStreamAsync()) as TestEntity;
@@ -279,15 +271,15 @@ await WithResponse(engine, "entity", HttpMethod.Post, entity, "text/xml", "text/
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestEntityAsYaml(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestEntityAsYaml(TestEngine engine, ExecutionMode mode)
{
const string entity = """
id: 1
nullable: 1234.56
""";
- await WithResponse(engine, "entity", HttpMethod.Post, entity, "application/yaml", "application/yaml", async r =>
+ await WithResponse(engine, mode, "entity", HttpMethod.Post, entity, "application/yaml", "application/yaml", async r =>
{
await r.AssertStatusAsync(HttpStatusCode.OK);
@@ -306,24 +298,24 @@ await WithResponse(engine, "entity", HttpMethod.Post, entity, "application/yaml"
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestException(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestException(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "exception", async r => { await r.AssertStatusAsync(HttpStatusCode.AlreadyReported); });
+ await WithResponse(engine, mode, "exception", async r => { await r.AssertStatusAsync(HttpStatusCode.AlreadyReported); });
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestDuplicate(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestDuplicate(TestEngine engine, ExecutionMode mode)
{
- await WithResponse(engine, "duplicate", async r => { await r.AssertStatusAsync(HttpStatusCode.BadRequest); });
+ await WithResponse(engine, mode, "duplicate", async r => { await r.AssertStatusAsync(HttpStatusCode.BadRequest); });
}
[TestMethod]
- [MultiEngineTest]
- public async Task TestWithInstance(TestEngine engine)
+ [MultiEngineFrameworkTest]
+ public async Task TestWithInstance(TestEngine engine, ExecutionMode mode)
{
- var layout = Layout.Create().AddService("t", new TestResource());
+ var layout = Layout.Create().AddService("t", new TestResource(), mode: mode);
await using var runner = await TestHost.RunAsync(layout);
@@ -344,11 +336,11 @@ public void TestConcernChaining()
#region Helpers
- private Task WithResponse(TestEngine engine, string uri, Func logic) => WithResponse(engine, uri, HttpMethod.Get, null, null, null, logic);
+ private Task WithResponse(TestEngine engine, ExecutionMode mode, string uri, Func logic) => WithResponse(engine, mode, uri, HttpMethod.Get, null, null, null, logic);
- private async Task WithResponse(TestEngine engine, string uri, HttpMethod method, string? body, string? contentType, string? accept, Func logic)
+ private async Task WithResponse(TestEngine engine, ExecutionMode mode, string uri, HttpMethod method, string? body, string? contentType, string? accept, Func logic)
{
- await using var service = await GetServiceAsync(engine);
+ await using var service = await GetServiceAsync(engine, mode);
var request = service.GetRequest($"/t/{uri}");
@@ -378,11 +370,12 @@ private async Task WithResponse(TestEngine engine, string uri, HttpMethod method
await logic(response);
}
- private static async Task GetServiceAsync(TestEngine engine)
+ private static async Task GetServiceAsync(TestEngine engine, ExecutionMode mode)
{
var service = ServiceResource.From()
.Serializers(Serialization.Default())
- .Injectors(Injection.Default());
+ .Injectors(Injection.Default())
+ .ExecutionMode(mode);
return await TestHost.RunAsync(Layout.Create().Add("t", service), engine: engine);
}
diff --git a/Testing/Acceptance/MultiEngineFrameworkTest.cs b/Testing/Acceptance/MultiEngineFrameworkTest.cs
new file mode 100644
index 000000000..69a441644
--- /dev/null
+++ b/Testing/Acceptance/MultiEngineFrameworkTest.cs
@@ -0,0 +1,40 @@
+using System.Reflection;
+using GenHTTP.Modules.Reflection;
+
+namespace GenHTTP.Testing.Acceptance;
+
+///
+/// When attributed on a test method, this attribute
+/// injects the both the engine as well the execution
+/// mode to be used.
+///
+[AttributeUsage(AttributeTargets.Method)]
+public class MultiEngineFrameworkTestAttribute : Attribute, ITestDataSource
+{
+
+ public IEnumerable