OpenAPI Metadata

As much as possible, Wolverine is trying to glean OpenAPI (Swashbuckle / Swagger) metadata from the method signature of the HTTP endpoint methods instead of forcing developers to add repetitive boilerplate code.

There’s a handful of predictable rules about metadata for Wolverine endpoints:

  • application/json is assumed for any request body type or any response body type
  • text/plain is the content type for any endpoint that returns a string as the response body
  • 200 and 500 are always assumed as valid status codes by default
  • 404 is also part of the metadata in most cases

That aside, there’s plenty of ways to modify the OpenAPI metadata for Wolverine endpoints for whatever you need. First off, all the attributes from ASP.Net Core that you use for MVC controller methods happily work on Wolverine endpoints:

public class SignupEndpoint
{
    // The first couple attributes are ASP.Net Core
    // attributes that add OpenAPI metadata to this endpoint
    [Tags("Users")]
    [ProducesResponseType(204)]
    [WolverinePost("/users/sign-up")]
    public static IResult SignUp(SignUpRequest request)
    {
        return Results.NoContent();
    }
}

snippet source | anchor

Or if you prefer the fluent interface from Minimal API, that’s actually supported as well for either individual endpoints or by policy directly on the HttpChain model:

public static void Configure(HttpChain chain)
{
    // This sample is from Wolverine itself on endpoints where all you do is forward
    // a request directly to a Wolverine messaging endpoint for later processing
    chain.Metadata.Add(builder =>
    {
        // Adding metadata
        builder.Metadata.Add(new WolverineProducesResponseTypeMetadata { StatusCode = 202, Type = null });
    });
    // This is run after all other metadata has been applied, even after the wolverine built-in metadata
    // So use this if you want to change or remove some metadata
    chain.Metadata.Finally(builder =>
    {
        builder.RemoveStatusCodeResponse(200);
    });
}

snippet source | anchor

::: tip For HTTP API versioning that partitions the OpenAPI output into one document per version, see the Versioning guide. It covers the multi-document SwaggerDoc setup, DocInclusionPredicate, DescribeWolverineApiVersions(), and Scalar integration. :::

Swashbuckle and Wolverine

Swashbuckle is de facto the OpenAPI tooling for ASP.Net Core applications. It’s also very MVC Core-centric in its assumptions about how to generate OpenAPI metadata to describe endpoints. If you need to (or just want to), you can do quite a bit to control exactly how Swashbuckle works against Wolverine endpoints by using a custom IOperationFilter of your making that can use Wolverine’s own HttpChain model for finer grained control. Here’s a sample from the Wolverine testing code that just uses Wolverine’ own model to determine the OpenAPI operation id:

// This class is NOT distributed in any kind of Nuget today, but feel very free
// to copy this code into your own as it is at least tested through Wolverine's
// CI test suite
public class WolverineOperationFilter : IOperationFilter // IOperationFilter is from Swashbuckle itself
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (context.ApiDescription.ActionDescriptor is WolverineActionDescriptor action)
        {
            operation.OperationId = action.Chain.OperationId;
        }
    }
}

snippet source | anchor

And that would be registered with Swashbuckle inside of your Program.Main() method like so:

builder.Services.AddSwaggerGen(x =>
{
    x.SwaggerDoc("default", new OpenApiInfo { Title = "Wolverine Web API", Version = "default" });
    x.SwaggerDoc("v1", new OpenApiInfo { Title = "Wolverine Web API v1", Version = "v1" });
    x.SwaggerDoc("v2", new OpenApiInfo { Title = "Wolverine Web API v2", Version = "v2" });
    x.SwaggerDoc("v3", new OpenApiInfo { Title = "Wolverine Web API v3", Version = "v3" });
    // v4 has no options.Deprecate("4.0") — used by integration tests to prove the
    // attribute-driven [ApiVersion("4.0", Deprecated = true)] is honoured on its own.
    x.SwaggerDoc("v4", new OpenApiInfo { Title = "Wolverine Web API v4", Version = "v4" });
    x.OperationFilter<WolverineOperationFilter>();
    x.OperationFilter<WolverineApiVersioningSwaggerOperationFilter>();
    x.DocInclusionPredicate((docName, api) =>
        docName == "default" || api.GroupName == docName);
    x.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
});

snippet source | anchor

Operation Id

::: warning You will have to use the custom WolverineOperationFilter in the previous section to relay Wolverine’s operation id determination to Swashbuckle. We have not (yet) been able to relay that information to Swashbuckle otherwise. :::

By default, Wolverine.HTTP is trying to mimic the logic for determining the OpenAPI operationId logic from MVC Core which is endpoint class name.method name. You can also override the operation id through the normal routing attribute through an optional property as shown below (from the Wolverine.HTTP test code):

// Override the operation id within the generated OpenAPI
// metadata
[WolverineGet("/fake/hello/async", OperationId = "OverriddenId")]
public Task<string> SayHelloAsync()
{
    return Task.FromResult("Hello");
}

snippet source | anchor

IHttpAware or IEndpointMetadataProvider Models

Wolverine honors the ASP.Net Core IEndpointMetadataProvider interface on resource types to add or modify endpoint metadata.

If you want Wolverine to automatically apply metadata (and HTTP runtime behavior) based on the resource type of an HTTP endpoint, you can have your response type implement the IHttpAware interface from Wolverine. As an example, consider the CreationResponse type in Wolverine:

/// <summary>
/// Base class for resource types that denote some kind of resource being created
/// in the system. Wolverine specific, and more efficient, version of Created<T> from ASP.Net Core
/// </summary>
public record CreationResponse([StringSyntax("Route")]string Url) : IHttpAware
{
    public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
    {
        builder.RemoveStatusCodeResponse(200);
 
        var create = new MethodCall(method.DeclaringType!, method).Creates.FirstOrDefault()?.VariableType;
        var metadata = new WolverineProducesResponseTypeMetadata { Type = create, StatusCode = 201 };
        builder.Metadata.Add(metadata);
    }
 
    void IHttpAware.Apply(HttpContext context)
    {
        context.Response.Headers.Location = Url;
        context.Response.StatusCode = 201;
    }
 
    public static CreationResponse<T> For<T>(T value, string url) => new CreationResponse<T>(url, value);
}

snippet source | anchor

Any endpoint that returns CreationResponse or a sub class will automatically expose a status code of 201 for successful processing to denote resource creation instead of the generic 200. Same goes for the built-in AcceptResponse type, but returning 202 status. Your own custom implementations of the IHttpAware interface would apply the metadata declarations at configuration time so that those customizations would be part of the exported Swashbuckle documentation of the system.

As of Wolverine 3.4, Wolverine will also apply OpenAPI metadata from any value created by compound handler middleware or other middleware that implements the IEndpointMetadataProvider interface — which many IResult implementations from within ASP.Net Core middleware do. Consider this example from the tests:

public class ValidatedCompoundEndpoint2
{
    public static User? Load(BlockUser2 cmd)
    {
        return cmd.UserId.IsNotEmpty() ? new User(cmd.UserId) : null;
    }
 
    // This method would be called, and if the NotFound value is
    // not null, will stop the rest of the processing
    // Likewise, Wolverine will use the NotFound type to add
    // OpenAPI metadata
    public static NotFound? Validate(User? user)
    {
        if (user == null)
            return (NotFound?)Results.NotFound<User>(user);
 
        return null;
    }
 
    [WolverineDelete("/optional/result")]
    public static  string Handle(BlockUser2 cmd, User user)
    {
        return "Ok - user blocked";
    }
}

snippet source | anchor

Generating the OpenAPI Document at the Command Line

::: tip Reach for this whenever Microsoft’s built-in OpenAPI generation chokes on your infrastructure. The standard build-time generators (Microsoft.Extensions.ApiDescription.Server and NSwag’s GetDocument.Insider) call IHost.StartAsync(), which boots Wolverine’s hosted service and tries to connect to your database and/or message broker before a single line of JSON is written — so they routinely fail in build/CI environments that have no real database or broker. The openapi command was purposefully designed to avoid that: it never starts your host, so it never opens a database or broker connection just to emit a JSON file. :::

Wolverine.HTTP adds an openapi command to the JasperFx command line (the same command line you already use for dotnet run -- codegen, dotnet run -- check-env, and friends). It reuses the host your application already builds — Wolverine added and MapWolverineEndpoints() already called — and asks the registered OpenAPI document provider (the Microsoft.AspNetCore.OpenApi service that Microsoft’s own GetDocument.Insider tool uses) to serialize the document directly from your endpoint metadata. The host is never started, so the result is functionally equivalent to Microsoft’s output without any of the startup connectivity.

The only prerequisite is that your application registers the built-in OpenAPI services and maps the Wolverine endpoints before handing control to the JasperFx command line:

builder.Services.AddOpenApi();      // Microsoft.AspNetCore.OpenApi
builder.Services.AddWolverineHttp();
 
var app = builder.Build();
 
app.MapWolverineEndpoints();
 
// The openapi command is dispatched from here
return await app.RunJasperFxCommands(args);

Then generate the document: