NativeLambdaRouter.SourceGenerator.OpenApi
1.4.0
See the version list below for details.
dotnet add package NativeLambdaRouter.SourceGenerator.OpenApi --version 1.4.0
NuGet\Install-Package NativeLambdaRouter.SourceGenerator.OpenApi -Version 1.4.0
<PackageReference Include="NativeLambdaRouter.SourceGenerator.OpenApi" Version="1.4.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="NativeLambdaRouter.SourceGenerator.OpenApi" Version="1.4.0" />
<PackageReference Include="NativeLambdaRouter.SourceGenerator.OpenApi"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add NativeLambdaRouter.SourceGenerator.OpenApi --version 1.4.0
#r "nuget: NativeLambdaRouter.SourceGenerator.OpenApi, 1.4.0"
#:package NativeLambdaRouter.SourceGenerator.OpenApi@1.4.0
#addin nuget:?package=NativeLambdaRouter.SourceGenerator.OpenApi&version=1.4.0
#tool nuget:?package=NativeLambdaRouter.SourceGenerator.OpenApi&version=1.4.0
NativeLambdaRouter.SourceGenerator.OpenApi
A Roslyn Source Generator that automatically generates OpenAPI 3.1 specifications from NativeLambdaRouter endpoint configurations at compile time.
Features
- Compile-Time Generation: OpenAPI specs are generated during build, not at runtime
- Zero Runtime Overhead: No reflection or dynamic code generation
- NativeLambdaRouter Integration: Automatic discovery of
MapGet,MapPost, etc. endpoints - Type-Safe: Extracts request/response types from generic parameters
- Schema Property Introspection: Generates real
propertiesandrequiredfrom C# record/class types - Nullable-Aware: Nullable properties are excluded from
requiredarrays - OpenAPI 3.1 Compliant: Generates valid OpenAPI 3.1 YAML specifications
Installation
dotnet add package NativeLambdaRouter.SourceGenerator.OpenApi
Quick Start
1. Define your routes
The generator automatically discovers endpoints in your ConfigureRoutes method:
public class MyRouter : RoutedApiGatewayFunction
{
protected override void ConfigureRoutes(IRouteBuilder routes)
{
routes.MapGet<GetItemsCommand, GetItemsResponse>("/v1/items", ctx => new GetItemsCommand());
routes.MapPost<CreateItemCommand, CreateItemResponse>("/v1/items", ctx => Deserialize<CreateItemCommand>(ctx.Body!));
routes.MapGet<GetItemByIdCommand, GetItemByIdResponse>("/v1/items/{id}", ctx => new GetItemByIdCommand(ctx.PathParameters["id"]));
routes.MapPut<UpdateItemCommand, UpdateItemResponse>("/v1/items/{id}", ctx => Deserialize<UpdateItemCommand>(ctx.Body!));
routes.MapDelete<DeleteItemCommand, DeleteItemResponse>("/v1/items/{id}", ctx => new DeleteItemCommand(ctx.PathParameters["id"]));
}
}
2. Build your project
The generator runs automatically during compilation. No additional configuration needed!
3. Access the generated spec
The class is generated in the {AssemblyName}.Generated namespace:
using MyProject.Generated;
// Get the full OpenAPI YAML specification
string yaml = GeneratedOpenApiSpec.YamlContent;
// Get endpoint count
int count = GeneratedOpenApiSpec.EndpointCount;
// Get list of all endpoints
foreach (var (method, path) in GeneratedOpenApiSpec.EndpointList)
{
Console.WriteLine($"{method} {path}");
}
// Use as IGeneratedOpenApiSpec (when NativeOpenApi is referenced)
IGeneratedOpenApiSpec spec = GeneratedOpenApiSpec.Instance;
Generated Output
The generator creates a GeneratedOpenApiSpec class in the {AssemblyName}.Generated namespace.
When NativeOpenApi is referenced, it implements IGeneratedOpenApiSpec:
// For assembly "MyProject" with NativeOpenApi referenced
namespace MyProject.Generated;
public sealed class GeneratedOpenApiSpec : Native.OpenApi.IGeneratedOpenApiSpec
{
public static readonly GeneratedOpenApiSpec Instance = new();
public const string YamlContent = @"
openapi: ""3.1.0""
info:
title: ""MyProject""
version: ""1.0.0""
paths:
/v1/items:
get:
operationId: getV1Items
...
";
public const int EndpointCount = 5;
public static readonly (string Method, string Path)[] EndpointList = new[]
{
("DELETE", "/v1/items/{id}"),
("GET", "/v1/items"),
("GET", "/v1/items/{id}"),
("POST", "/v1/items"),
("PUT", "/v1/items/{id}"),
};
}
Without NativeOpenApi, the class is standalone (no interface).
Supported Mapping Methods
The generator detects all NativeLambdaRouter mapping methods:
| Method | HTTP Verb |
|---|---|
MapGet<TCommand, TResponse> |
GET |
MapPost<TCommand, TResponse> |
POST |
MapPut<TCommand, TResponse> |
PUT |
MapDelete<TCommand, TResponse> |
DELETE |
MapPatch<TCommand, TResponse> |
PATCH |
Map<TCommand, TResponse> |
Custom (first argument) |
Generated Features
For each endpoint, the generator creates:
- Operation ID: Auto-generated from HTTP method and path (e.g.,
getV1Items) - Summary: Generated from command type name
- Tags: Extracted from path segments for grouping
- Security: JWT Bearer authentication by default
- Parameters: Path parameters extracted from route template
- Request Body: For POST, PUT, PATCH methods with schema reference
- Responses: Success response with schema + standard error responses
Schema Property Generation
The generator introspects C# types via Roslyn to produce real OpenAPI schemas with properties, types, formats, and required fields.
Supported Type Mappings
| C# Type | OpenAPI Type | Format |
|---|---|---|
string |
string |
— |
int |
integer |
int32 |
long |
integer |
int64 |
float |
number |
float |
double |
number |
double |
decimal |
number |
double |
bool |
boolean |
— |
DateTime, DateTimeOffset |
string |
date-time |
DateOnly |
string |
date |
Guid |
string |
uuid |
Uri |
string |
uri |
List<T>, T[], IReadOnlyList<T> |
array |
items: {T} |
Dictionary<K,V> |
object |
— |
| Enum types | string |
enum: [values] |
| Complex types | — | $ref: "#/components/schemas/TypeName" |
Example
public sealed record CreateRoleRequest(
string RealmId,
string Name,
string? Description, // nullable → not in required
List<string>? PermissionIds, // nullable array → not in required
string PerformedBy);
public sealed record CreateRoleResponse(string Id, string Name);
Generates:
components:
schemas:
CreateRoleRequest:
type: object
properties:
realmId:
type: string
name:
type: string
description:
type: string
permissionIds:
type: array
items:
type: string
performedBy:
type: string
required:
- realmId
- name
- performedBy
CreateRoleResponse:
type: object
properties:
id:
type: string
name:
type: string
required:
- id
- name
Integration with NativeOpenApi
Combine with NativeOpenApi for a complete solution:
using Native.OpenApi;
using MyProject.Generated;
// Use the generated spec as part of your document loading
public class MyOpenApiDocumentLoader : OpenApiDocumentLoaderBase
{
public MyOpenApiDocumentLoader(OpenApiResourceReader reader) : base(reader) { }
public override IReadOnlyList<OpenApiDocumentPart> LoadCommon() => [];
public override IReadOnlyList<OpenApiDocumentPart> LoadPartials()
{
return new List<OpenApiDocumentPart>
{
// Load from generated spec via IGeneratedOpenApiSpec
LoadFromGeneratedSpec("my-api", GeneratedOpenApiSpec.Instance),
// Load additional partials
Load("schemas", "openapi/schemas.yaml")
};
}
}
Multi-Project Architecture
The generator uses the assembly name as namespace, so each project gets its own unique class:
Functions.Admin → namespace Functions.Admin.Generated → GeneratedOpenApiSpec
Functions.Identity → namespace Functions.Identity.Generated → GeneratedOpenApiSpec
Functions.OpenId → namespace Functions.OpenId.Generated → GeneratedOpenApiSpec
When NativeOpenApi is referenced, the generated class implements IGeneratedOpenApiSpec,
enabling the Functions.OpenApi project to merge all specs polymorphically:
// In Functions.OpenApi — merges all generated specs into a single document
public class ConsolidatedOpenApiDocumentLoader : OpenApiDocumentLoaderBase
{
public ConsolidatedOpenApiDocumentLoader(OpenApiResourceReader reader) : base(reader) { }
public override IReadOnlyList<OpenApiDocumentPart> LoadCommon()
{
return [Load("schemas", "openapi/schemas.yaml"),
Load("responses", "openapi/responses.yaml"),
Load("security", "openapi/security.yaml")];
}
public override IReadOnlyList<OpenApiDocumentPart> LoadPartials()
{
return [LoadFromGeneratedSpec("admin", Functions.Admin.Generated.GeneratedOpenApiSpec.Instance),
LoadFromGeneratedSpec("identity", Functions.Identity.Generated.GeneratedOpenApiSpec.Instance),
LoadFromGeneratedSpec("openid", Functions.OpenId.Generated.GeneratedOpenApiSpec.Instance)];
}
}
Each Function project only needs:
<PackageReference Include="NativeLambdaRouter.SourceGenerator.OpenApi" Version="1.3.3" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<PackageReference Include="NativeOpenApi" Version="1.3.3" />
And the consolidator project:
<PackageReference Include="NativeOpenApi" Version="1.3.3" />
<ProjectReference Include="..\Functions.Admin\Functions.Admin.csproj" />
<ProjectReference Include="..\Functions.Identity\Functions.Identity.csproj" />
<ProjectReference Include="..\Functions.OpenId\Functions.OpenId.csproj" />
Custom Namespace with OpenApiSpecName (v1.3.1+)
By default the generator uses the assembly name as the namespace base. This works fine in most cases, but AWS Lambda custom runtime projects typically set AssemblyName=bootstrap for all functions — causing namespace collisions.
Use the OpenApiSpecName MSBuild property to override the namespace base:
<PropertyGroup>
<AssemblyName>bootstrap</AssemblyName>
<OpenApiSpecName>NativeGuardBackend.Functions.Admin</OpenApiSpecName>
<OpenApiSpecTitle>Admin API</OpenApiSpecTitle>
</PropertyGroup>
This generates:
namespace NativeGuardBackend.Functions.Admin.Generated;
public sealed class GeneratedOpenApiSpec : Native.OpenApi.IGeneratedOpenApiSpec
{
// YAML title: "Admin API"
public const string YamlContent = @"...";
}
Priority:
| Property | Fallback | Used For |
|---|---|---|
OpenApiSpecName |
AssemblyName |
Namespace base ({value}.Generated) |
OpenApiSpecTitle |
OpenApiSpecName (dots → spaces) |
YAML info.title field |
Example for multi-Lambda setup:
<PropertyGroup>
<AssemblyName>bootstrap</AssemblyName>
<OpenApiSpecName>NativeGuardBackend.Functions.Admin</OpenApiSpecName>
</PropertyGroup>
<PropertyGroup>
<AssemblyName>bootstrap</AssemblyName>
<OpenApiSpecName>NativeGuardBackend.Functions.Identity</OpenApiSpecName>
</PropertyGroup>
<PropertyGroup>
<AssemblyName>bootstrap</AssemblyName>
<OpenApiSpecName>NativeGuardBackend.Functions.OpenId</OpenApiSpecName>
</PropertyGroup>
Each produces a unique namespace even though all assemblies are named bootstrap.
Using ProjectReference Instead of NuGet
When developing locally or contributing to this repository, you may reference the Source Generator via ProjectReference instead of PackageReference:
<ProjectReference Include="path\to\NativeLambdaRouter.SourceGenerator.OpenApi.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
Important: The
.propsfile that exposesOpenApiSpecNameandOpenApiSpecTitleto the Roslyn analyzer is only auto-imported for NuGet packages. When usingProjectReference, you must manually declareCompilerVisiblePropertyin your project or aDirectory.Build.props:
<Project>
<ItemGroup>
<CompilerVisibleProperty Include="OpenApiSpecName" />
<CompilerVisibleProperty Include="OpenApiSpecTitle" />
</ItemGroup>
</Project>
Without this, the Source Generator cannot read MSBuild properties and will fall back to AssemblyName for namespace and title.
Automated YAML Extraction (Multi-Lambda)
For multi-Lambda architectures where producers cannot be referenced by the consumer (due to AssemblyName=bootstrap ambiguity), you can use an MSBuild inline task to automatically extract the generated YAML on every build. See the MultiLambdaSample for a complete working example with Directory.Build.targets.
Requirements
- .NET 6.0 or later (for Source Generator support)
- NativeLambdaRouter for endpoint definitions
- C# 9.0 or later
How It Works
- Syntax Analysis: The generator scans your code for
Map*method invocations - Semantic Analysis: Validates that calls are on
IRouteBuilderand extracts type information - Code Generation: Creates the
GeneratedOpenApiSpecclass with the OpenAPI YAML - Build Integration: The generated file is automatically included in compilation — no extra configuration needed
Note: The generated class is injected directly into the compilation in memory. You do not need to add
EmitCompilerGeneratedFilesto your.csprojfor the generator to work. That setting only saves a physical copy of the generated.csfiles to disk for inspection/debug purposes.
<details> <summary>🔍 Debug: Inspecting generated files on disk</summary>
If you want to see the generated .cs files physically on disk, add to your .csproj:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
The files will be saved to:
obj/Debug/net10.0/generated/NativeLambdaRouter.SourceGenerator.OpenApi/
NativeLambdaRouter.SourceGenerator.OpenApi.OpenApiSourceGenerator/
GeneratedOpenApiSpec.g.cs
</details>
Related Packages
- NativeOpenApi - OpenAPI document loading, linting, merging, and rendering
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.