Expand description
§doxa
Ergonomic OpenAPI documentation for axum services. Built on top of
utoipa and utoipa_axum, this crate provides:
- An
ApiDocBuilderfor assembling an OpenAPI document from autoipa::openapi::OpenApivalue, finalizing it into an in-memoryApiDocwhose serialized JSON is shared via a reference-countedbytes::Bytesbuffer. - A
mount_docshelper that mountsGET /openapi.jsonplus an interactive documentation UI on an existingaxum::Router, all from memory — the spec is never written to disk. - An RFC 7807
ProblemDetailsresponse body usable as the default error schema across a project.
All UI integrations are feature-gated independently. The default
feature set enables docs-scalar which mounts
the Scalar API reference UI from a CDN-loaded HTML template,
rendered out of the box with the three-pane modern layout, dark
mode on, the schemas index hidden, the codegen sidebar suppressed,
and Scalar’s paid product upsells (Agent / MCP) disabled. Every
one of those choices is overridable via ScalarConfig passed
through MountOpts::scalar. Scalar is preferred because it is
actively maintained, parses OpenAPI 3.2 natively, renders the
x-badges vendor extension, and surfaces required OAuth2 scopes
inline under each operation — covering per-operation permission
requirements produced by extractor-side DocOperationSecurity
impls.
§Tour
The full surface of the crate, end to end. Every macro, derive,
extractor, and builder method that ships in the default feature set
appears in the snippet below and the whole thing compiles under
cargo test --doc.
use axum::Json;
use doxa::{
routes, ApiDocBuilder, ApiErrorBody, ApiResult, DocumentedHeader, Header,
MountDocsExt, MountOpts, OpenApiRouter, ScalarConfig, ScalarLayout, ScalarTheme,
SseEvent, SseEventMeta, SseSpecVersion, SseStream, ToSchema,
};
use doxa::{get, post, ApiError};
use futures_core::Stream;
use serde::{Deserialize, Serialize};
use std::convert::Infallible;
// ----- Typed error envelope ----------------------------------------------
//
// `#[derive(ApiError)]` wires both `IntoResponse` and `IntoResponses`
// from per-variant `#[api(status, code)]` attributes. Multiple
// variants may share a status — they are grouped into one OpenAPI
// response with separate examples.
#[derive(Debug, thiserror::Error, Serialize, ToSchema, ApiError)]
enum WidgetError {
#[error("validation failed: {0}")]
#[api(status = 400, code = "validation_error")]
Validation(String),
#[error("conflict: {0}")]
#[api(status = 400, code = "conflict")]
Conflict(String),
#[error("not found")]
#[api(status = 404, code = "not_found")]
NotFound,
#[error("internal error")]
#[api(status = 500, code = "internal")]
Internal,
}
// ----- Typed request / response bodies -----------------------------------
#[derive(Debug, Serialize, ToSchema)]
struct Widget { id: u32, name: String }
#[derive(Debug, Deserialize, ToSchema)]
struct CreateWidget { name: String }
// ----- Typed header extractor --------------------------------------------
//
// Implementing `DocumentedHeader` on a marker type lets the same
// marker drive both extraction (via `Header<XApiKey>`) and OpenAPI
// documentation. The macro recognizes `Header<H>` in the handler
// signature and emits the corresponding params block automatically.
struct XApiKey;
impl DocumentedHeader for XApiKey {
fn name() -> &'static str { "X-Api-Key" }
fn description() -> &'static str { "Tenant API key" }
}
// ----- SSE event stream --------------------------------------------------
//
// `#[derive(SseEvent)]` provides the per-variant event name; pair
// it with `serde::Serialize` and `ToSchema` so the wire format and
// OpenAPI schema stay aligned. `SseStream<E, S>` is the response
// wrapper — handlers never construct axum's `Sse` directly.
#[derive(Serialize, ToSchema, SseEvent)]
#[serde(tag = "event", content = "data", rename_all = "snake_case")]
enum BuildEvent {
Started { id: u64 },
Progress { done: u64, total: u64 },
#[sse(name = "finished")]
Completed,
}
// ----- Handlers ----------------------------------------------------------
/// Create a widget. The path uses the `#[post]` shortcut, takes a
/// typed JSON body and a typed header, and returns an
/// `ApiResult<Json<T>, E>` so successes and the full error
/// vocabulary both flow into the OpenAPI document.
#[post("/widgets", tag = "Widgets")]
async fn create_widget(
Header(_key, ..): Header<XApiKey>,
Json(req): Json<CreateWidget>,
) -> ApiResult<(axum::http::StatusCode, Json<Widget>), WidgetError> {
if req.name.is_empty() {
return Err(WidgetError::Validation("name is required".into()));
}
Ok((
axum::http::StatusCode::CREATED,
Json(Widget { id: 42, name: req.name }),
))
}
/// Stream build progress as Server-Sent Events. The macro
/// recognizes `SseStream<E, _>` and emits a `text/event-stream`
/// response with one `oneOf` branch per `SseEvent` variant.
#[get("/builds/{id}/events", tag = "Builds")]
async fn stream_build(
) -> SseStream<BuildEvent, impl Stream<Item = Result<BuildEvent, Infallible>>> {
let events = futures::stream::iter(vec![
Ok(BuildEvent::Started { id: 1 }),
Ok(BuildEvent::Progress { done: 1, total: 10 }),
Ok(BuildEvent::Completed),
]);
SseStream::new(events)
}
// ----- Compose, finalize, mount -----------------------------------------
let (router, openapi) = OpenApiRouter::<()>::new()
.routes(routes!(create_widget))
.routes(routes!(stream_build))
.split_for_parts();
let api_doc = ApiDocBuilder::new()
.title("Widgets API")
.version("1.0.0")
.description("Tour service")
.bearer_security("bearerAuth")
.tag_group("Core", ["Widgets"])
.tag_group("Streaming", ["Builds"])
// Use OpenAPI 3.2 `itemSchema` for SSE responses (the default).
.sse_openapi_version(SseSpecVersion::V3_2)
.merge(openapi)
.build();
// Customize the Scalar UI: classic single-column layout with a
// light theme, dark mode off. `MountOpts::default()` keeps the
// historical three-pane modern dark-mode appearance.
let app = router.mount_docs(
api_doc,
MountOpts::default()
.scalar(
ScalarConfig::default()
.layout(ScalarLayout::Classic)
.theme(ScalarTheme::Solarized)
.dark_mode(false),
),
);The crate’s public surface contains no project-specific types —
everything is generic over utoipa’s native types so it can be lifted
into any axum project.
Macros§
- routes
- Wraps
utoipa_axum::routes!and augments the schemas vector with everything each handler’s argument types reference.
Structs§
- ApiDoc
- Immutable, in-memory OpenAPI document with its serialized JSON form
pre-rendered into a
Bytesbuffer. - ApiDoc
Builder - Builder for an
ApiDoc. - ApiError
Body - Envelope for HTTP error responses produced by
#[derive(ApiError)]. - Badge
Contribution - One badge entry the layer attaches to every operation it covers.
Surfaces in doc UIs that render the
x-badgesvendor extension (Scalar): a colored chip with the supplied name. - DocHeader
Entry - Generic
IntoParamsimplementor that produces one header parameter from aDocumentedHeadermarker, callingH::name,H::description, andH::exampleat runtime. - Header
- Extracts a header value by name, where the name is supplied
type-level via
DocumentedHeader::name. Modeled afteraxum_extra::TypedHeaderbut using our ownDocumentedHeadertrait so the macro pass can resolve the wire name at runtime without depending on the foreignheaderscrate. - Header
Param - One header parameter descriptor. Construct via
HeaderParam::typed/HeaderParam::typed_optionalfor type-safe declarations driven byDocumentedHeader, or viaHeaderParam::required/HeaderParam::optionalfor ad-hoc string names. - Layer
Contribution - What a layer contributes to the OpenAPI contract for the
operations it covers. Build with
LayerContribution::newand the chainablewith_*setters; empty fields are zero-cost. - Mount
Opts - Configuration for
mount_docs. - OpenApi
- Root object of the OpenAPI document.
- Open
ApiRouter - A wrapper struct for
axum::Routerandutoipa::openapi::OpenApifor composing handlers and services with collecting OpenAPI information from the handlers. - Problem
Details - RFC 7807 problem details body.
- Response
Contribution - One extra response status the layer can return (e.g. 401 from auth middleware, 429 from a rate limiter). Skipped on operations that already declare a response with the same status — handler-level declarations always win.
- Scalar
Config - Scalar UI rendering options.
- Security
Contribution - One security requirement entry the layer enforces. References a
scheme that has been registered with
crate::ApiDocBuilder::bearer_securityorcrate::ApiDocBuilder::security_scheme— a dangling reference produces an invalid spec, so make sure the scheme name matches. - SseStream
- A typed SSE response stream.
Enums§
- Build
Error - Errors that can occur while
ApiDocBuilder::building anApiDoc. - Developer
Tools - Visibility of Scalar’s developer-tools drawer.
- Document
Download - Format(s) offered by the header “Download OpenAPI” button.
- Scalar
Layout - Page layout variants.
- Scalar
Theme - Visual theme presets recognized by Scalar.
- SseSpec
Version - OpenAPI spec version used to render Server-Sent Event (SSE) responses in the generated document.
Traits§
- Apidoc
Handler Ops - Per-handler hook that mutates the
Operationgenerated by#[utoipa::path]for this handler. Implemented by the method macro on the same__path_<fn>struct that carries theutoipapath metadata; the impl iterates every handler argument through thecrate::DocOperationSecurityprobe. - Apidoc
Handler Schemas - Reports the full set of schemas a handler’s arguments reference.
The method macro emits an impl for the dispatch struct; the
extended
routes!macro calls it to extend the OpenAPI router’s schema collection before the router is merged. - DocHeader
Params - Contributes header parameters to an operation.
- DocOperation
Security - Extractor-side contribution of per-operation security/permission metadata. Implemented by per-route guards (e.g. a permission extractor that names the action it requires) so the resulting OpenAPI operation documents the requirement.
- DocPath
Params - Contributes path parameters to an operation.
- DocPath
Scalar - Scalar/tuple path impl trait. Kept separate from
DocPathParamsto avoid overlap with the struct-form impl. - DocQuery
Params - Contributes query parameters to an operation.
- DocRequest
Body - Contributes the request body schema to an operation.
- DocResponse
Body - Describe how a handler’s return type contributes to its OpenAPI operation’s 200 response.
- Documented
Header - Type-level descriptor for one HTTP header.
- Documented
Layer - A tower
Layerthat declares its own OpenAPI contribution. - Inner
ToSchema - Contributes schemas referenced by an extractor’s inner payload into the OpenAPI document’s component registry.
- Into
Params - Trait used to convert implementing type to OpenAPI parameters.
- Into
Responses - This trait is implemented to document a type (like an enum) which can represent multiple responses, to be used in operation.
- Mount
Docs Ext - Extension trait providing
mount_docsas a fluent method onaxum::Router. Equivalent to the free functionmount_docs. - Open
ApiRouter Ext - Adds
OpenApiRouterExt::layer_documentedandOpenApiRouterExt::tag_alltoOpenApiRouter. Implemented for every state type the underlying router supports. - Path
Scalar - Sealed trait for primitives usable as scalar
Pathparameters. Implementations supply the OpenAPI schema for the parameter — done manually (rather than viautoipa::PartialSchema) because common scalar path types likeUuidare recognized by utoipa only via token inspection in derives, not through aPartialSchemaimpl. - SseEvent
Meta - Per-variant metadata for a Server-Sent Event enum.
- ToSchema
- Trait for implementing OpenAPI Schema object.
Functions§
- apply_
badge_ to_ operation - Append an
x-badgesentry shaped for Scalar’s native badge renderer ({name, color}) to a single operation. Idempotent onname— calling twice with the same name leaves a single badge entry.coloraccepts any CSS color value (keyword, hex, rgb, hsl, or a Scalar CSS custom property likevar(--scalar-color-accent)). - apply_
contribution - Apply a contribution to every operation in
openapi. Public so callers who hold anutoipa::openapi::OpenApidirectly can also annotate it without going throughcrate::OpenApiRouterExt. - mount_
docs - Mount the OpenAPI JSON endpoint (and a documentation UI, if a UI feature is enabled) on the supplied router.
- operation_
for_ method_ mut - Find the operation slot on a
PathItemfor a givenHttpMethod. Used by the method-macro-generatedApidocHandlerOpsimpl to pick out the operation matching the handler’s declared methods. - record_
required_ permission - Record a per-operation permission requirement on
op, emitting:
Type Aliases§
- ApiResult
- Convenience alias for handler return types whose error half implements
IntoResponses. Equivalent toResult<T, E>but signals intent.
Attribute Macros§
- capability
- Declare a
Capablemarker type backed by aCapabilityconstant. - delete
#[delete("/path", ...)]shortcut for [utoipa::path]. Seegetfor the inference rules.- get
- Shortcut for
#[utoipa::path(get, path = "...")]. - operation
- Generic operation attribute for cases where the HTTP method must be specified explicitly (multi-method routes, non-standard verbs).
- patch
#[patch("/path", ...)]shortcut for [utoipa::path]. Seegetfor the inference rules.- post
#[post("/path", ...)]shortcut for [utoipa::path]. Seegetfor the inference rules.- put
#[put("/path", ...)]shortcut for [utoipa::path]. Seegetfor the inference rules.
Derive Macros§
- ApiError
- Derive [
axum::response::IntoResponse] and [utoipa::IntoResponses] for an error enum from a single per-variant declaration. - Into
Params - Generate path parameters from struct’s fields.
- Into
Responses - Generate responses with status codes what
can be attached to the
utoipa::path. - SseEvent
- Derive
SseEventMetafor an enum whose variants represent the events of a Server-Sent Event stream. - ToSchema
- Generate reusable OpenAPI schema to be used
together with
OpenApi.