Skip to content

Native API versioning for Wolverine.Http (closes #2627)#2633

Merged
jeremydmiller merged 3 commits intoJasperFx:mainfrom
outofrange-consulting:feature/api-versioning-v1
May 1, 2026
Merged

Native API versioning for Wolverine.Http (closes #2627)#2633
jeremydmiller merged 3 commits intoJasperFx:mainfrom
outofrange-consulting:feature/api-versioning-v1

Conversation

@outofrange-consulting
Copy link
Copy Markdown

@outofrange-consulting outofrange-consulting commented Apr 30, 2026

Closes #2627. Sibling reference: #2401.

Summary

Adds native API versioning to WolverineFx.Http with first-class SwaggerUI / Scalar compatibility. Single dependency on Asp.Versioning.Abstractions (no Asp.Versioning.Http, no Microsoft.AspNetCore.OpenApi).

[ApiVersion("1.0")]
public static class OrdersV1Endpoint
{
    [WolverineGet("/orders")] public static OrdersV1Response Get() => new(...);
}

app.MapWolverineEndpoints(opts =>
{
    opts.UseApiVersioning(v =>
    {
        v.Sunset("3.0").On(DateTimeOffset.Parse("2027-01-01")).WithLink(new Uri("https://example.com/migrate"));
        v.Deprecate("1.0").On(DateTimeOffset.Parse("2026-12-31"));
    });
});

Produces /v1/orders, /v2/orders, /v3/orders automatically, plus per-version OpenAPI documents and the right RFC headers on every response.

What's included

  • URL-segment versioning. [ApiVersion("1.0")]/v1/orders by default. UrlSegmentPrefix and UrlSegmentVersionFormatter are both overridable.
  • Method-wins attribute resolution. Method-level [ApiVersion] takes precedence over class-level. Multiple [ApiVersion] attributes on a single method are explicitly rejected at startup.
  • UnversionedPolicy with three modes: PassThrough (default), RequireExplicit (throws at startup if any chain lacks [ApiVersion]), AssignDefault (assigns a configured DefaultVersion).
  • Sunset / deprecation policies with fluent Sunset("1.0").On(date).WithLink(uri, title?, type?) builders. Attribute-driven [ApiVersion(..., Deprecated = true)] takes precedence over options-driven deprecation per chain.
  • Response headers: RFC 9745 Deprecation (with true token when no date), RFC 8594 Sunset, RFC 8288 Link, plus api-supported-versions. Two independent toggles (EmitDeprecationHeaders, EmitApiSupportedVersionsHeader).
  • Duplicate detection. Two chains with the same (verb, original-route, version) triple throw at bootstrap with both display names in the message.
  • OpenAPI document partitioning via IEndpointGroupNameMetadata (already plumbed through to ApiDescription.GroupName). WolverineApiVersioningOpenApiOptions.DocumentNameStrategy lets users customise the document name (default v{major}).
  • app.DescribeWolverineApiVersions() returns one description per discovered version for wiring SwaggerUI / Scalar dropdowns.
  • Sample Swashbuckle IOperationFilter (WolverineApiVersioningSwaggerOperationFilter) lives in WolverineWebApi as copy-paste reference code — sets OpenApiOperation.Deprecated and emits an x-api-versioning extension carrying sunset date and links.

Dependency choice

Asp.Versioning.Abstractions 10.0.0 only — not Asp.Versioning.Http. Routing is driven from IHttpPolicy, so we need the types (ApiVersion, ApiVersionAttribute, SunsetPolicy, DeprecationPolicy, ApiVersionMetadata, LinkHeaderValue) but not the readers, version sets, or DI policy manager. Smaller surface, no MVC-centric assumptions.

Asp.Versioning.Http is verified absent from the transitive closure (dotnet list package --include-transitive).

Default URL-segment format

[ApiVersion("1.0")] produces /v1/orders (major-only) by default. Users wanting /v1.0/orders set UrlSegmentVersionFormatter = v => v.ToString(). Documentation explicitly notes both options.

Implementation notes

  • Wolverine.Http does not have an internal OpenAPI document builder; it surfaces metadata via IApiDescription and external tools (Swashbuckle, MS.AspNetCore.OpenApi) read it. The policy attaches IEndpointGroupNameMetadata so document partitioning works automatically with Swashbuckle's DocInclusionPredicate or the equivalent ASP.NET Core mechanism.
  • ApiVersionEndpointHeaderState and ApiVersionHeaderWriter are public. The state record is part of the observable contract for user OpenAPI filters; the writer is referenced by Wolverine's dynamic codegen (empirically verified — internal produces CS0122 in generated source).

Tests

Suite Count Status
Full Wolverine.Http.Tests (net9.0) 707
ApiVersioning suite 73
Swashbuckle integration 7
Alba integration scenarios (api_versioning_integration_tests.cs) 10

Smoke test on the WolverineWebApi sample confirms /v1/orders, /v2/orders, /v3/orders all 200 with the expected Deprecation / Sunset / Link / api-supported-versions headers, and /swagger/{default,v1,v2,v3}/swagger.json all serve correctly.

Test plan

  • dotnet build succeeds on net8.0 / net9.0 / net10.0
  • dotnet test src/Http/Wolverine.Http.Tests is green
  • Run WolverineWebApi and visit /swagger — confirm dropdown shows default, v1, v2, v3 and the v1 endpoint shows the deprecated marker
  • Visit /scalar/v1 — confirm the Scalar UI renders versioned operations
  • curl -i /v1/orders shows Deprecation, Link, api-supported-versions headers
  • curl -i /v3/orders shows Sunset, Link, api-supported-versions headers
  • dotnet list src/Http/Wolverine.Http package --include-transitive confirms Asp.Versioning.Http is not present

Documentation

docs/guide/http/versioning.md covers attribute rules, URL-segment strategy, the three UnversionedPolicy modes, sunset/deprecation policies, OpenAPI integration (Swashbuckle + Scalar + MS.AspNetCore.OpenApi). Cross-linked from metadata.md. Sidebar entry registered in docs/.vitepress/config.mts.

Geoffrey MARC added 3 commits April 30, 2026 14:19
Adds URL-segment API versioning to Wolverine.Http driven from an
IHttpPolicy. Single dependency on Asp.Versioning.Abstractions 10.0.0
(no Asp.Versioning.Http).

* HttpChain.ApiVersion / SunsetPolicy / DeprecationPolicy properties
* WolverineHttpOptions.UseApiVersioning configuration surface with
  fluent Sunset / Deprecate per-version builders
* [ApiVersion] attribute resolver — method-wins-over-class, multi-version
  handlers rejected at startup
* ApiVersioningPolicy: route rewriting, sunset/deprecation propagation,
  duplicate (verb,route,version) detection, IEndpointGroupNameMetadata
  for OpenAPI document partitioning, Asp.Versioning.ApiVersionMetadata
  for downstream tooling
* ApiVersionHeaderWriter: RFC 9745 Deprecation, RFC 8594 Sunset,
  RFC 8288 Link, api-supported-versions response headers
* DescribeWolverineApiVersions extension for SwaggerUI / Scalar
  dropdown wiring
* WolverineApiVersioningOpenApiOptions.DocumentNameStrategy for
  customising per-version OpenAPI document names
* OrdersV1Endpoint, OrdersV2Endpoint, OrdersV3PreviewEndpoint sharing
  the same /orders route — versioning policy rewrites them to
  /v1/orders, /v2/orders, /v3/orders
* WolverineApiVersioningSwaggerOperationFilter reference filter that
  surfaces deprecation + sunset metadata into OpenAPI operations
* Multi-document Swashbuckle setup (default + v1/v2/v3) wired against
  DescribeWolverineApiVersions for the SwaggerUI dropdown
* Scalar.AspNetCore wired for an alternative API explorer UI
* Alba integration tests covering route rewriting, response headers,
  per-version OpenAPI document partitioning, and deprecation flag
* IntegrationContext / swashbuckle_integration updated to read the
  new "default" Swashbuckle document (was implicitly v1)
New docs/guide/http/versioning.md covers attribute usage,
UnversionedPolicy modes, sunset/deprecation policies, RFC 9745/8594
header behaviour, and OpenAPI integration patterns for Swashbuckle,
Scalar, and Microsoft.AspNetCore.OpenApi. Cross-linked from
metadata.md and registered in the Vitepress sidebar.
@jeremydmiller
Copy link
Copy Markdown
Member

@outofrange-consulting There's enough for another release tomorrow. You okay with this as is? I'll review too, but from your perspective?

@outofrange-consulting
Copy link
Copy Markdown
Author

I am, I stongly advice you to review the API introduced and the dependency, and I’ll be happy the take it further if you want header/querystring support later ;)

@jeremydmiller
Copy link
Copy Markdown
Member

@outofrange-consulting Let's make somebody ask for the header/querystring usage later. I'm starting a review.

@jeremydmiller
Copy link
Copy Markdown
Member

@outofrange-consulting We're going for it. It's a little convenient maybe that this will get in just before Wolverine 6, so we can make any API adjustments based on feedback for 6.0.

Thanks for taking this on!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improved support for Wolverine.Http versionning

2 participants