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 typetext/plain
is the content type for any endpoint that returns a string as the response body200
and500
are always assumed as valid status codes by default404
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();
}
}
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);
});
}
Swashbuckle and Wolverine
Swashbuckle is de facto the default OpenAPI tooling and it is added in by the default dotnet new
templates 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;
}
}
}
And that would be registered with Swashbuckle inside of your Program.Main()
method like so:
builder.Services.AddSwaggerGen(x =>
{
x.OperationFilter<WolverineOperationFilter>();
});
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");
}
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);
}
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";
}
}