diff --git a/Directory.Build.props b/Directory.Build.props
index 319aa4c..6f44e36 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,30 +1,29 @@
-
- latest
- net8.0
+
+ latest
- enable
- $(WarningsAsErrors);nullable;
+ enable
+ $(WarningsAsErrors);nullable;
- enable
+ enable
- latest-all
- true
+ latest-all
+ true
- true
- true
+ true
+ true
- false
-
+ false
+
-
-
-
+
+
+
-
- true
- true
- true
- opencover
-
+
+ true
+ true
+ true
+ opencover
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 92efaaf..7b19cf5 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,36 +1,37 @@
- true
+ true
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/Immediate.Apis.sln b/Immediate.Apis.sln
index dd28897..98dde4d 100644
--- a/Immediate.Apis.sln
+++ b/Immediate.Apis.sln
@@ -23,18 +23,53 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{681429
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{4A2C6A96-653A-40CC-9354-535F92A0BBD8}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Immediate.Apis", "src\Immediate.Apis\Immediate.Apis.csproj", "{EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Immediate.Apis.Generators", "src\Immediate.Apis.Generators\Immediate.Apis.Generators.csproj", "{01D48E54-9A25-4795-B154-19F97C306FC2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Immediate.Apis.Shared", "src\Immediate.Apis.Shared\Immediate.Apis.Shared.csproj", "{0EF463C2-6237-4491-A446-80860ACD280D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Immediate.Apis.FunctionalTests", "tests\Immediate.Apis.FunctionalTests\Immediate.Apis.FunctionalTests.csproj", "{4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Immediate.Apis.Tests", "tests\Immediate.Apis.Tests\Immediate.Apis.Tests.csproj", "{058EFC02-6DDA-486F-8C4F-08BA816061CE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129}.Release|Any CPU.Build.0 = Release|Any CPU
+ {01D48E54-9A25-4795-B154-19F97C306FC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {01D48E54-9A25-4795-B154-19F97C306FC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {01D48E54-9A25-4795-B154-19F97C306FC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {01D48E54-9A25-4795-B154-19F97C306FC2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0EF463C2-6237-4491-A446-80860ACD280D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0EF463C2-6237-4491-A446-80860ACD280D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0EF463C2-6237-4491-A446-80860ACD280D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0EF463C2-6237-4491-A446-80860ACD280D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {058EFC02-6DDA-486F-8C4F-08BA816061CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {058EFC02-6DDA-486F-8C4F-08BA816061CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {058EFC02-6DDA-486F-8C4F-08BA816061CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {058EFC02-6DDA-486F-8C4F-08BA816061CE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{CA4DD675-66D2-45F3-B57B-F751AF1E66D3} = {AF02160B-E028-444A-9B6E-B548463E4A4D}
+ {EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129} = {68142973-97B4-45D0-B6C7-BE884990AB7E}
+ {01D48E54-9A25-4795-B154-19F97C306FC2} = {68142973-97B4-45D0-B6C7-BE884990AB7E}
+ {0EF463C2-6237-4491-A446-80860ACD280D} = {68142973-97B4-45D0-B6C7-BE884990AB7E}
+ {4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3} = {4A2C6A96-653A-40CC-9354-535F92A0BBD8}
+ {058EFC02-6DDA-486F-8C4F-08BA816061CE} = {4A2C6A96-653A-40CC-9354-535F92A0BBD8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E947E0E4-B196-46E5-8CFE-A6AE390B8897}
diff --git a/src/Immediate.Apis.Generators/EquatableReadOnlyList.cs b/src/Immediate.Apis.Generators/EquatableReadOnlyList.cs
new file mode 100644
index 0000000..cca38ea
--- /dev/null
+++ b/src/Immediate.Apis.Generators/EquatableReadOnlyList.cs
@@ -0,0 +1,53 @@
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Immediate.Apis.Generators;
+
+[ExcludeFromCodeCoverage]
+public static class EquatableReadOnlyList
+{
+ public static EquatableReadOnlyList ToEquatableReadOnlyList(this IEnumerable enumerable)
+ => new(enumerable.ToArray());
+}
+
+///
+/// A wrapper for IReadOnlyList that provides value equality support for the wrapped list.
+///
+[ExcludeFromCodeCoverage]
+public readonly struct EquatableReadOnlyList(
+ IReadOnlyList? collection
+) : IEquatable>, IReadOnlyList
+{
+ private IReadOnlyList Collection => collection ?? [];
+
+ public bool Equals(EquatableReadOnlyList other)
+ => this.SequenceEqual(other);
+
+ public override bool Equals(object? obj)
+ => obj is EquatableReadOnlyList other && Equals(other);
+
+ public override int GetHashCode()
+ {
+ var hashCode = new HashCode();
+
+ foreach (var item in Collection)
+ hashCode.Add(item);
+
+ return hashCode.ToHashCode();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => Collection.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => Collection.GetEnumerator();
+
+ public int Count => Collection.Count;
+ public T this[int index] => Collection[index];
+
+ public static bool operator ==(EquatableReadOnlyList left, EquatableReadOnlyList right)
+ => left.Equals(right);
+
+ public static bool operator !=(EquatableReadOnlyList left, EquatableReadOnlyList right)
+ => !left.Equals(right);
+}
diff --git a/src/Immediate.Apis.Generators/Immediate.Apis.Generators.csproj b/src/Immediate.Apis.Generators/Immediate.Apis.Generators.csproj
new file mode 100644
index 0000000..e5e0987
--- /dev/null
+++ b/src/Immediate.Apis.Generators/Immediate.Apis.Generators.csproj
@@ -0,0 +1,43 @@
+
+
+
+ netstandard2.0
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(GetTargetPathDependsOn);GetDependencyTargetPaths
+
+
+
+
+
+
+
+
+
+
+ minor
+ preview.0
+ v
+
+
+
diff --git a/src/Immediate.Apis.Generators/ImmediateApisGenerator.cs b/src/Immediate.Apis.Generators/ImmediateApisGenerator.cs
new file mode 100644
index 0000000..d07ae52
--- /dev/null
+++ b/src/Immediate.Apis.Generators/ImmediateApisGenerator.cs
@@ -0,0 +1,174 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+
+namespace Immediate.Apis.Generators;
+
+[Generator]
+public sealed class ImmediateApisGenerator : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var methods = GetMapMethods(context, "Get")
+ .Concat(
+ GetMapMethods(context, "Post"),
+ GetMapMethods(context, "Put"),
+ GetMapMethods(context, "Patch"),
+ GetMapMethods(context, "Delete")
+ );
+
+ var assemblyName = context.CompilationProvider
+ .Select((cp, _) => cp.AssemblyName!
+ .Replace(".", string.Empty)
+ .Replace(" ", string.Empty)
+ .Trim()
+ );
+
+ context.RegisterSourceOutput(
+ methods.Combine(assemblyName),
+ (spc, m) => RenderMethods(spc, m.Left, m.Right)
+ );
+ }
+
+ private static IncrementalValueProvider> GetMapMethods(
+ IncrementalGeneratorInitializationContext context,
+ string method
+ ) => context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ $"Immediate.Apis.Shared.Map{method}Attribute",
+ (_, _) => true,
+ TransformMethod
+ )
+ .Where(m => m != null)
+ .Collect()!;
+
+ private sealed record Method
+ {
+ public required string Route { get; init; }
+ public required string ClassName { get; init; }
+ public required string MethodName { get; init; }
+ public required string ParameterType { get; init; }
+ public required bool AllowAnonymous { get; init; }
+ public required bool Authorize { get; init; }
+ public required string? AuthorizePolicy { get; init; }
+ }
+
+ private static Method? TransformMethod(
+ GeneratorAttributeSyntaxContext context,
+ CancellationToken token
+ )
+ {
+ token.ThrowIfCancellationRequested();
+
+ var symbol = (INamedTypeSymbol)context.TargetSymbol;
+ var displayName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+
+ token.ThrowIfCancellationRequested();
+
+ var attribute = context.Attributes[0];
+ var method = attribute.AttributeClass!.Name[..^9];
+ var route = (string?)attribute.ConstructorArguments.FirstOrDefault().Value;
+
+ if (route == null)
+ return null;
+
+ token.ThrowIfCancellationRequested();
+
+ if (symbol.ContainingType is not null)
+ return null;
+
+ var attributes = symbol.GetAttributes()
+ .Select(a => a.AttributeClass?.ToString() ?? "")
+ .ToList();
+
+ if (!attributes.Contains("Immediate.Handlers.Shared.HandlerAttribute"))
+ return null;
+
+ token.ThrowIfCancellationRequested();
+
+ if (symbol
+ .GetMembers()
+ .OfType()
+ .Where(m => m.IsStatic)
+ .Where(m =>
+ m.Name.Equals("Handle", StringComparison.Ordinal)
+ || m.Name.Equals("HandleAsync", StringComparison.Ordinal)
+ )
+ .ToList() is not [var handleMethod])
+ {
+ return null;
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ // must have request type and cancellation token
+ if (handleMethod.Parameters.Length < 2)
+ return null;
+
+ var requestType = handleMethod.Parameters[0].Type
+ .ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+
+ token.ThrowIfCancellationRequested();
+
+ var allowAnonymous = attributes.Contains("Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute");
+
+ var authorizeIndex = attributes.IndexOf("Microsoft.AspNetCore.Authorization.AuthorizeAttribute");
+ var authorize = authorizeIndex >= 0;
+ var authorizePolicy = string.Empty;
+
+ if (authorize)
+ {
+ var authorizeAttribute = symbol.GetAttributes()[authorizeIndex];
+ if (authorizeAttribute.ConstructorArguments.Length > 0)
+ {
+ authorizePolicy = (string)authorizeAttribute.ConstructorArguments[0].Value!;
+ }
+ else if (authorizeAttribute.NamedArguments.Length > 0)
+ {
+ foreach (var argument in authorizeAttribute.NamedArguments)
+ {
+ if (argument.Key != "Policy")
+ return null;
+
+ authorizePolicy = (string)argument.Value.Value!;
+ }
+ }
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ return new()
+ {
+ Route = route,
+ ClassName = displayName,
+ MethodName = method,
+ ParameterType = requestType,
+ AllowAnonymous = allowAnonymous,
+ Authorize = authorize,
+ AuthorizePolicy = authorizePolicy
+ };
+ }
+
+ private static void RenderMethods(
+ SourceProductionContext context,
+ EquatableReadOnlyList methods,
+ string assemblyName
+ )
+ {
+ if (methods.Count == 0)
+ return;
+
+ var token = context.CancellationToken;
+
+ var template = Utility.GetTemplate("Routes");
+ token.ThrowIfCancellationRequested();
+
+ var source = template.Render(new
+ {
+ Assembly = assemblyName,
+ Methods = methods,
+ });
+
+ token.ThrowIfCancellationRequested();
+ context.AddSource("RoutesBuilder.g.cs", source);
+ }
+}
diff --git a/src/Immediate.Apis.Generators/Properties/launchSettings.json b/src/Immediate.Apis.Generators/Properties/launchSettings.json
new file mode 100644
index 0000000..df45684
--- /dev/null
+++ b/src/Immediate.Apis.Generators/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Immediate.Apis.FunctionalTests": {
+ "commandName": "DebugRoslynComponent",
+ "targetProject": "../../tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.csproj"
+ }
+ }
+}
diff --git a/src/Immediate.Apis.Generators/Templates/Routes.sbntxt b/src/Immediate.Apis.Generators/Templates/Routes.sbntxt
new file mode 100644
index 0000000..e829f27
--- /dev/null
+++ b/src/Immediate.Apis.Generators/Templates/Routes.sbntxt
@@ -0,0 +1,33 @@
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class {{ assembly }}RoutesBuilder
+{
+ public static IEndpointRouteBuilder Map{{ assembly }}Endpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ {{~ for m in methods ~}}
+ _ = app
+ .{{ m.method_name }}(
+ "{{ m.route }}",
+ async (
+ [AsParameters] {{ m.parameter_type }} parameters,
+ [FromServices] {{ m.class_name }}.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ {{~ if m.allow_anonymous ~}}
+ .AllowAnonymous()
+ {{~ else if m.authorize ~}}
+ .RequireAuthorization({{ if !string.empty m.authorize_policy }}"{{ m.authorize_policy }}"{{ end }})
+ {{~ end ~}}
+ ;
+
+ {{~ end ~}}
+ return app;
+ }
+}
diff --git a/src/Immediate.Apis.Generators/Utility.cs b/src/Immediate.Apis.Generators/Utility.cs
new file mode 100644
index 0000000..e46d155
--- /dev/null
+++ b/src/Immediate.Apis.Generators/Utility.cs
@@ -0,0 +1,39 @@
+using System.Collections.Immutable;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+using Scriban;
+
+namespace Immediate.Apis.Generators;
+
+internal static class Utility
+{
+ public static Template GetTemplate(string name)
+ {
+ using var stream = Assembly
+ .GetExecutingAssembly()
+ .GetManifestResourceStream(
+ $"Immediate.Apis.Generators.Templates.{name}.sbntxt"
+ )!;
+
+ using var reader = new StreamReader(stream);
+ return Template.Parse(reader.ReadToEnd());
+ }
+
+ public static IncrementalValueProvider> Concat(
+ this IncrementalValueProvider> method1,
+ IncrementalValueProvider> method2,
+ IncrementalValueProvider> method3,
+ IncrementalValueProvider> method4,
+ IncrementalValueProvider> method5
+ ) => method1.Combine(method2)
+ .Combine(method3.Combine(method4))
+ .Combine(method5)
+ .Select((x, _) =>
+ new EquatableReadOnlyList([
+ .. x.Left.Left.Left,
+ .. x.Left.Left.Right,
+ .. x.Left.Right.Left,
+ .. x.Left.Right.Right,
+ .. x.Right,
+ ]));
+}
diff --git a/src/Immediate.Apis.Shared/.editorconfig b/src/Immediate.Apis.Shared/.editorconfig
new file mode 100644
index 0000000..31f894b
--- /dev/null
+++ b/src/Immediate.Apis.Shared/.editorconfig
@@ -0,0 +1,7 @@
+[*.cs]
+
+# XML Documentation
+dotnet_diagnostic.CS0105.severity = error # CS0105: Using directive is unnecessary.
+dotnet_diagnostic.CS1573.severity = error # CS1573: Missing XML comment for parameter
+dotnet_diagnostic.CS1591.severity = error # CS1591: Missing XML comment for publicly visible type or member
+dotnet_diagnostic.CS1712.severity = error # CS1712: Type parameter has no matching typeparam tag in the XML comment (but other type parameters do)
diff --git a/src/Immediate.Apis.Shared/Immediate.Apis.Shared.csproj b/src/Immediate.Apis.Shared/Immediate.Apis.Shared.csproj
new file mode 100644
index 0000000..89ea4d0
--- /dev/null
+++ b/src/Immediate.Apis.Shared/Immediate.Apis.Shared.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netstandard2.0
+ false
+ $(NoWarn);CA1716
+
+
+
+
+
+
+
diff --git a/src/Immediate.Apis.Shared/MapDeleteAttribute.cs b/src/Immediate.Apis.Shared/MapDeleteAttribute.cs
new file mode 100644
index 0000000..66bbbd0
--- /dev/null
+++ b/src/Immediate.Apis.Shared/MapDeleteAttribute.cs
@@ -0,0 +1,8 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Immediate.Apis.Shared;
+
+///
+public sealed class MapDeleteAttribute(
+ [StringSyntax("Route")] string route
+) : MapMethodAttribute(route);
diff --git a/src/Immediate.Apis.Shared/MapGetAttribute.cs b/src/Immediate.Apis.Shared/MapGetAttribute.cs
new file mode 100644
index 0000000..cddf5a4
--- /dev/null
+++ b/src/Immediate.Apis.Shared/MapGetAttribute.cs
@@ -0,0 +1,8 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Immediate.Apis.Shared;
+
+///
+public sealed class MapGetAttribute(
+ [StringSyntax("Route")] string route
+) : MapMethodAttribute(route);
diff --git a/src/Immediate.Apis.Shared/MapMethodAttribute.cs b/src/Immediate.Apis.Shared/MapMethodAttribute.cs
new file mode 100644
index 0000000..9693fb6
--- /dev/null
+++ b/src/Immediate.Apis.Shared/MapMethodAttribute.cs
@@ -0,0 +1,16 @@
+namespace Immediate.Apis.Shared;
+
+///
+/// Applied to a class to indicate that minimal APIs registration should be generated
+///
+///
+/// The route that the handler should be registered with
+///
+[AttributeUsage(AttributeTargets.Class)]
+public abstract class MapMethodAttribute(string route) : Attribute
+{
+ ///
+ /// The route that the handler should be registered with
+ ///
+ public string Route { get; } = route;
+}
diff --git a/src/Immediate.Apis.Shared/MapPatchAttribute.cs b/src/Immediate.Apis.Shared/MapPatchAttribute.cs
new file mode 100644
index 0000000..ca521f4
--- /dev/null
+++ b/src/Immediate.Apis.Shared/MapPatchAttribute.cs
@@ -0,0 +1,8 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Immediate.Apis.Shared;
+
+///
+public sealed class MapPatchAttribute(
+ [StringSyntax("Route")] string route
+) : MapMethodAttribute(route);
diff --git a/src/Immediate.Apis.Shared/MapPostAttribute.cs b/src/Immediate.Apis.Shared/MapPostAttribute.cs
new file mode 100644
index 0000000..6ada2bd
--- /dev/null
+++ b/src/Immediate.Apis.Shared/MapPostAttribute.cs
@@ -0,0 +1,8 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Immediate.Apis.Shared;
+
+///
+public sealed class MapPostAttribute(
+ [StringSyntax("Route")] string route
+) : MapMethodAttribute(route);
diff --git a/src/Immediate.Apis.Shared/MapPutAttribute.cs b/src/Immediate.Apis.Shared/MapPutAttribute.cs
new file mode 100644
index 0000000..79c953a
--- /dev/null
+++ b/src/Immediate.Apis.Shared/MapPutAttribute.cs
@@ -0,0 +1,8 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Immediate.Apis.Shared;
+
+///
+public sealed class MapPutAttribute(
+ [StringSyntax("Route")] string route
+) : MapMethodAttribute(route);
diff --git a/src/Immediate.Apis/Immediate.Apis.csproj b/src/Immediate.Apis/Immediate.Apis.csproj
new file mode 100644
index 0000000..d6ed2fb
--- /dev/null
+++ b/src/Immediate.Apis/Immediate.Apis.csproj
@@ -0,0 +1,54 @@
+
+
+
+ netstandard2.0
+ true
+ false
+
+
+
+ Immediate.Apis
+ An implementation of the mediator pattern in .NET using source-generation.
+
+ Immediate.Apis Developers
+
+ MIT
+ readme.md
+
+ true
+ https://github.com/viceroypenguin/Immediate.Apis
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ minor
+ preview.0
+ v
+
+
+
diff --git a/tests/Immediate.Apis.FunctionalTests/.editorconfig b/tests/Immediate.Apis.FunctionalTests/.editorconfig
new file mode 100644
index 0000000..b9ff461
--- /dev/null
+++ b/tests/Immediate.Apis.FunctionalTests/.editorconfig
@@ -0,0 +1,3 @@
+[*.cs]
+
+dotnet_diagnostic.CA1716.severity = none # CA1716: Identifiers should not match keywords
diff --git a/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Get.cs b/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Get.cs
new file mode 100644
index 0000000..25dbe7f
--- /dev/null
+++ b/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Get.cs
@@ -0,0 +1,32 @@
+using Immediate.Apis.Shared;
+using Immediate.Handlers.Shared;
+using Microsoft.AspNetCore.Authorization;
+
+namespace Immediate.Apis.FunctionalTests.Features.WeatherForecast;
+
+[Handler]
+[MapGet("/forecast")]
+[AllowAnonymous]
+public static partial class Get
+{
+ public sealed record Query;
+
+ public sealed record Result
+ {
+ public required DateOnly Date { get; init; }
+ public required int TemperatureC { get; init; }
+ public required string? Summary { get; init; }
+ public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
+ }
+
+ private static async ValueTask> Handle(
+ Query _,
+ CancellationToken token
+ )
+ {
+ await Task.Delay(1, token);
+ return [
+ new() { Date = new(2024, 1, 1), TemperatureC = 0, Summary = "Sunny and Freezing" },
+ ];
+ }
+}
diff --git a/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Put.cs b/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Put.cs
new file mode 100644
index 0000000..6f19ac4
--- /dev/null
+++ b/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Put.cs
@@ -0,0 +1,25 @@
+using Immediate.Apis.Shared;
+using Immediate.Handlers.Shared;
+
+namespace Immediate.Apis.FunctionalTests.Features.WeatherForecast;
+
+[Handler]
+[MapPut("/forecast")]
+public static partial class Put
+{
+ public sealed record Command
+ {
+ public required DateOnly Date { get; init; }
+ public required int TemperatureC { get; init; }
+ public required string? Summary { get; init; }
+ public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
+ }
+
+ private static async ValueTask Handle(
+ Command _,
+ CancellationToken token
+ )
+ {
+ await Task.Delay(1, token);
+ }
+}
diff --git a/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.csproj b/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.csproj
new file mode 100644
index 0000000..26116e3
--- /dev/null
+++ b/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.http b/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.http
new file mode 100644
index 0000000..240422e
--- /dev/null
+++ b/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.http
@@ -0,0 +1,6 @@
+@Immediate.Apis.FunctionalTests_HostAddress = http://localhost:5233
+
+GET {{Immediate.Apis.FunctionalTests_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/tests/Immediate.Apis.FunctionalTests/Program.cs b/tests/Immediate.Apis.FunctionalTests/Program.cs
new file mode 100644
index 0000000..c74ee77
--- /dev/null
+++ b/tests/Immediate.Apis.FunctionalTests/Program.cs
@@ -0,0 +1,20 @@
+using Immediate.Apis.FunctionalTests;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+builder.Services.AddHandlers();
+
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+app.UseSwagger();
+app.UseSwaggerUI();
+
+app.MapImmediateApisFunctionalTestsEndpoints();
+
+app.Run();
diff --git a/tests/Immediate.Apis.FunctionalTests/Properties/launchSettings.json b/tests/Immediate.Apis.FunctionalTests/Properties/launchSettings.json
new file mode 100644
index 0000000..153261d
--- /dev/null
+++ b/tests/Immediate.Apis.FunctionalTests/Properties/launchSettings.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:62891",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5233",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/tests/Immediate.Apis.FunctionalTests/appsettings.Development.json b/tests/Immediate.Apis.FunctionalTests/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/tests/Immediate.Apis.FunctionalTests/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/tests/Immediate.Apis.FunctionalTests/appsettings.json b/tests/Immediate.Apis.FunctionalTests/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/tests/Immediate.Apis.FunctionalTests/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/tests/Immediate.Apis.Tests/.editorconfig b/tests/Immediate.Apis.Tests/.editorconfig
new file mode 100644
index 0000000..7944128
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/.editorconfig
@@ -0,0 +1,14 @@
+[*.g.verified.cs]
+
+# Mark code as excluded and generated
+# NB: This disables regular analyzers by default
+exclude = true
+generated_code = true
+
+# Disable any active Roslyn analyzers
+dotnet_analyzer_diagnostic.severity = none
+
+# These are security analyzers and must be manually disabled
+dotnet_diagnostic.CA5350.severity = none
+dotnet_diagnostic.CA5351.severity = none
+dotnet_diagnostic.CA5394.severity = none
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Delete#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..85b4d8f
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Delete#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapDelete(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .AllowAnonymous()
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Get#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..949c5a6
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Get#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapGet(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .AllowAnonymous()
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Patch#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..b425cd9
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Patch#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPatch(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .AllowAnonymous()
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Post#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..6450780
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Post#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPost(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .AllowAnonymous()
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Put#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..1822407
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Put#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPut(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .AllowAnonymous()
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.cs
new file mode 100644
index 0000000..905f4eb
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.cs
@@ -0,0 +1,46 @@
+namespace Immediate.Apis.Tests.GeneratorTests;
+
+public sealed class ApiAllowAnonymousTests
+{
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public async Task MapMethodWithAllowAnonymousTest(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+ using Microsoft.AspNetCore.Authorization;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}("/test")]
+ [AllowAnonymous]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask HandleAsync(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ _ = Assert.Single(result.GeneratedTrees);
+
+ _ = await Verify(result)
+ .UseParameters(method);
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Delete#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..0387c50
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Delete#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapDelete(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization("TestPolicy")
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Get#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..56df973
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Get#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapGet(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization("TestPolicy")
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Patch#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..b53320c
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Patch#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPatch(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization("TestPolicy")
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Post#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..1a7f346
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Post#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPost(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization("TestPolicy")
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Put#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..a0a81a3
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Put#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPut(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization("TestPolicy")
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Delete#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..0387c50
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Delete#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapDelete(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization("TestPolicy")
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Get#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..56df973
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Get#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapGet(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization("TestPolicy")
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Patch#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..b53320c
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Patch#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPatch(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization("TestPolicy")
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Post#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..1a7f346
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Post#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPost(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization("TestPolicy")
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Put#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..a0a81a3
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Put#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPut(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization("TestPolicy")
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Delete#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..4c21f3f
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Delete#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapDelete(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization()
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Get#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..854a8bf
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Get#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapGet(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization()
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Patch#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..ecd13f0
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Patch#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPatch(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization()
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Post#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..a2f4adb
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Post#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPost(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization()
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Put#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..d62a271
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Put#RoutesBuilder.g.verified.cs
@@ -0,0 +1,28 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPut(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ .RequireAuthorization()
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.cs
new file mode 100644
index 0000000..02343c5
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.cs
@@ -0,0 +1,130 @@
+namespace Immediate.Apis.Tests.GeneratorTests;
+
+public sealed class ApiAuthorizeTests
+{
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public async Task MapMethodWithSimpleAuthorizeTest(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+ using Microsoft.AspNetCore.Authorization;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}("/test")]
+ [Authorize]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask HandleAsync(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ _ = Assert.Single(result.GeneratedTrees);
+
+ _ = await Verify(result)
+ .UseParameters(method);
+ }
+
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public async Task MapMethodWithAuthorizeConstructorTest(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+ using Microsoft.AspNetCore.Authorization;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}("/test")]
+ [Authorize("TestPolicy")]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask HandleAsync(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ _ = Assert.Single(result.GeneratedTrees);
+
+ _ = await Verify(result)
+ .UseParameters(method);
+ }
+
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public async Task MapMethodWithAuthorizeNamedPolicyArgumentTest(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+ using Microsoft.AspNetCore.Authorization;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}("/test")]
+ [Authorize(Policy = "TestPolicy")]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask HandleAsync(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ _ = Assert.Single(result.GeneratedTrees);
+
+ _ = await Verify(result)
+ .UseParameters(method);
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/GeneratorTestHelper.cs b/tests/Immediate.Apis.Tests/GeneratorTests/GeneratorTestHelper.cs
new file mode 100644
index 0000000..6f9f26d
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/GeneratorTestHelper.cs
@@ -0,0 +1,42 @@
+using Immediate.Apis.Generators;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace Immediate.Apis.Tests.GeneratorTests;
+
+public static class GeneratorTestHelper
+{
+ public static GeneratorDriver GetDriver(string source)
+ {
+ // Parse the provided string into a C# syntax tree
+ var syntaxTree = CSharpSyntaxTree.ParseText(source);
+
+ // Create a Roslyn compilation for the syntax tree.
+ var compilation = CSharpCompilation.Create(
+ assemblyName: "Tests",
+ syntaxTrees: [syntaxTree],
+ references:
+ [
+ .. Basic.Reference.Assemblies.Net80.References.All,
+ MetadataReference.CreateFromFile("./Immediate.Handlers.Shared.dll"),
+ MetadataReference.CreateFromFile("./Immediate.Apis.Shared.dll"),
+ MetadataReference.CreateFromFile("./Microsoft.Extensions.DependencyInjection.dll"),
+ MetadataReference.CreateFromFile("./Microsoft.Extensions.DependencyInjection.Abstractions.dll"),
+ MetadataReference.CreateFromFile("./Microsoft.AspNetCore.Authorization.dll"),
+ MetadataReference.CreateFromFile("./Microsoft.AspNetCore.Metadata.dll"),
+ MetadataReference.CreateFromFile("./Microsoft.Extensions.Logging.Abstractions.dll"),
+ MetadataReference.CreateFromFile("./Microsoft.Extensions.Options.dll"),
+ MetadataReference.CreateFromFile("./Microsoft.Extensions.Primitives.dll"),
+ ]
+ );
+
+ // Create an instance of our incremental source generator
+ var generator = new ImmediateApisGenerator();
+
+ // The GeneratorDriver is used to run our generator against a compilation
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
+
+ // Run the source generator!
+ return driver.RunGenerators(compilation);
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/InvalidCodeTests.cs b/tests/Immediate.Apis.Tests/GeneratorTests/InvalidCodeTests.cs
new file mode 100644
index 0000000..d60d531
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/InvalidCodeTests.cs
@@ -0,0 +1,260 @@
+namespace Immediate.Apis.Tests.GeneratorTests;
+
+public sealed class InvalidCodeTests
+{
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public void MissingRoute(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}()]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask Handle(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Empty(result.GeneratedTrees);
+ }
+
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public void MissingHandlerAttribute(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+
+ namespace Dummy;
+
+ [Map{{method}}("/test")]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask Handle(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Empty(result.GeneratedTrees);
+ }
+
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public void NestedClass(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+
+ namespace Dummy;
+
+ public static class Outer
+ {
+ [Handler]
+ [Map{{method}}("/test")]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask Handle(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Empty(result.GeneratedTrees);
+ }
+
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public void MissingHandler(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}("/test")]
+ public static class GetUsersQuery
+ {
+ public record Query;
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Empty(result.GeneratedTrees);
+ }
+
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public void InvalidHandler(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}("/test")]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask Handle(
+ Query _)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Empty(result.GeneratedTrees);
+ }
+
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public void AuthorizeUsesAuthenticationSchemes(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+ using Microsoft.AspNetCore.Authorization;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}("/test")]
+ [Authorize(AuthenticationSchemes = "test")]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask HandleAsync(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Empty(result.GeneratedTrees);
+ }
+
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public void AuthorizeUsesRoles(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+ using Microsoft.AspNetCore.Authorization;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}("/test")]
+ [Authorize(Roles = "test")]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask HandleAsync(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ Assert.Empty(result.GeneratedTrees);
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Delete#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..02a9210
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Delete#RoutesBuilder.g.verified.cs
@@ -0,0 +1,27 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapDelete(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Get#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..5da5de2
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Get#RoutesBuilder.g.verified.cs
@@ -0,0 +1,27 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapGet(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Patch#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..5c5f0e8
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Patch#RoutesBuilder.g.verified.cs
@@ -0,0 +1,27 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPatch(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Post#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..05c9595
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Post#RoutesBuilder.g.verified.cs
@@ -0,0 +1,27 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPost(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Put#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..b5ca151
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Put#RoutesBuilder.g.verified.cs
@@ -0,0 +1,27 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPut(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Delete#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..02a9210
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Delete#RoutesBuilder.g.verified.cs
@@ -0,0 +1,27 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapDelete(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Get#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..5da5de2
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Get#RoutesBuilder.g.verified.cs
@@ -0,0 +1,27 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapGet(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Patch#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..5c5f0e8
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Patch#RoutesBuilder.g.verified.cs
@@ -0,0 +1,27 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPatch(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Post#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..05c9595
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Post#RoutesBuilder.g.verified.cs
@@ -0,0 +1,27 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPost(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Put#RoutesBuilder.g.verified.cs
new file mode 100644
index 0000000..b5ca151
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Put#RoutesBuilder.g.verified.cs
@@ -0,0 +1,27 @@
+//HintName: RoutesBuilder.g.cs
+using Microsoft.AspNetCore.Mvc;
+
+#pragma warning disable CS1591
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class TestsRoutesBuilder
+{
+ public static IEndpointRouteBuilder MapTestsEndpoints(
+ this IEndpointRouteBuilder app
+ )
+ {
+ _ = app
+ .MapPut(
+ "/test",
+ async (
+ [AsParameters] global::Dummy.GetUsersQuery.Query parameters,
+ [FromServices] global::Dummy.GetUsersQuery.Handler handler,
+ CancellationToken token
+ ) => await handler.HandleAsync(parameters, token)
+ )
+ ;
+
+ return app;
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.cs
new file mode 100644
index 0000000..a2e3713
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.cs
@@ -0,0 +1,84 @@
+namespace Immediate.Apis.Tests.GeneratorTests;
+
+public sealed class SimpleApiTests
+{
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public async Task MapMethodHandleTest(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}("/test")]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask Handle(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ _ = Assert.Single(result.GeneratedTrees);
+
+ _ = await Verify(result)
+ .UseParameters(method);
+ }
+
+ [Theory]
+ [InlineData("Get")]
+ [InlineData("Post")]
+ [InlineData("Patch")]
+ [InlineData("Put")]
+ [InlineData("Delete")]
+ public async Task MapMethodHandleAsyncTest(string method)
+ {
+ var driver = GeneratorTestHelper.GetDriver(
+ $$"""
+ using System.Threading.Tasks;
+ using Immediate.Apis.Shared;
+ using Immediate.Handlers.Shared;
+
+ namespace Dummy;
+
+ [Handler]
+ [Map{{method}}("/test")]
+ public static class GetUsersQuery
+ {
+ public record Query;
+
+ private static ValueTask HandleAsync(
+ Query _,
+ CancellationToken token)
+ {
+ return 0;
+ }
+ }
+ """);
+
+ var result = driver.GetRunResult();
+
+ Assert.Empty(result.Diagnostics);
+ _ = Assert.Single(result.GeneratedTrees);
+
+ _ = await Verify(result)
+ .UseParameters(method);
+ }
+}
diff --git a/tests/Immediate.Apis.Tests/Immediate.Apis.Tests.csproj b/tests/Immediate.Apis.Tests/Immediate.Apis.Tests.csproj
new file mode 100644
index 0000000..908ef06
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/Immediate.Apis.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Immediate.Apis.Tests/ModuleInitializer.cs b/tests/Immediate.Apis.Tests/ModuleInitializer.cs
new file mode 100644
index 0000000..d9900e5
--- /dev/null
+++ b/tests/Immediate.Apis.Tests/ModuleInitializer.cs
@@ -0,0 +1,13 @@
+using System.Runtime.CompilerServices;
+
+namespace Immediate.Apis.Tests;
+
+public static class ModuleInitializer
+{
+ [ModuleInitializer]
+ public static void Init()
+ {
+ VerifierSettings.AutoVerify(includeBuildServer: false);
+ VerifySourceGenerators.Initialize();
+ }
+}