Expand description
Procedural macros for doxa.
§Derive macros
ApiError— wires an error enum into bothaxum::response::IntoResponseandutoipa::IntoResponsesfrom a single per-variant#[api(...)]declaration. Multiple variants sharing a status code are grouped into one OpenAPI response with distinct examples. An optionaloutcomeattribute integrates with the audit trail.SseEvent— implementsSseEventMetafor a tagged enum soSseStreamnames each SSE frame after the variant carrying it. Override names with#[sse(name = "…")].
§HTTP method attribute macros
get, post, put, patch,
delete delegate to utoipa::path with automatic inference
from the handler signature. Use operation for custom or
multi-method routes.
§What the method macros infer
operation_id— defaults to the function name.request_body— detected from the firstJson<T>parameter, including through transparent wrappers likeValid<Json<T>>.- Path parameters —
{name}segments in the route template are matched toPath<T>extractors (scalar, tuple, and struct forms). - Query parameters —
Query<T>extractors (including wrapped) contribute query parameters via trait dispatch. - Header parameters —
Header<H>extractors contribute header parameters. Theheaders(H1, H2)attribute documents headers without extracting them; both forms deduplicate. - Success response —
Json<T>→ 200;(StatusCode, Json<T>)→ 201;SseStream<E, _>→text/event-streamwith per-variant event names. - Error responses — the
EfromResult<_, E>is folded intoresponses(...)as anIntoResponsesreference. - Tags —
tag = "Name"for a single tag,tags("A", "B")for multiple. Tags control grouping in documentation UIs.
Explicit overrides always win: if you supply request_body = ...,
params(...), or responses(...) by hand, inference for that field
is suppressed.
§Capability attribute macro
capability declares a Capable marker type backed by a
Capability constant for use with doxa_auth::Require<M>.
§Usage
Consumers should depend on doxa (with the default macros
feature) and import these macros via doxa::{get, post, ApiError, SseEvent, …} rather than depending on this crate
directly.
§Tour
Every macro the crate exports, exercised end-to-end. Compiles
under cargo test --doc.
use axum::Json;
use doxa::{
routes, ApiDocBuilder, ApiResult, DocumentedHeader, Header,
MountDocsExt, MountOpts, OpenApiRouter, SseEventMeta, SseStream, ToSchema,
};
use doxa::{get, post, ApiError, SseEvent};
use futures_core::Stream;
use serde::{Deserialize, Serialize};
use std::convert::Infallible;
// -- ApiError: multi-variant-per-status grouping --------------------------
#[derive(Debug, thiserror::Error, Serialize, ToSchema, ApiError)]
enum WidgetError {
#[error("validation failed: {0}")]
#[api(status = 400, code = "validation_error")]
Validation(String),
// Second variant at the same status — the OpenAPI spec emits one
// 400 response with two named examples.
#[error("conflict: {0}")]
#[api(status = 400, code = "conflict")]
Conflict(String),
#[error("not found")]
#[api(status = 404, code = "not_found")]
NotFound,
}
// -- SseEvent: variant-tagged event stream --------------------------------
#[derive(Serialize, ToSchema, SseEvent)]
#[serde(tag = "event", content = "data", rename_all = "snake_case")]
enum BuildEvent {
Started { id: u64 },
Progress { done: u64, total: u64 },
// Override the default snake-case event name.
#[sse(name = "finished")]
Completed,
}
// -- DocumentedHeader: typed header on the handler signature --------------
struct XApiKey;
impl DocumentedHeader for XApiKey {
fn name() -> &'static str { "X-Api-Key" }
fn description() -> &'static str { "Tenant API key" }
}
// -- Method shortcuts: tags, request body, headers, Result return --------
#[derive(Debug, Serialize, ToSchema)]
struct Widget { id: u32, name: String }
#[derive(Debug, Deserialize, ToSchema)]
struct CreateWidget { name: String }
/// Single tag — forwarded to utoipa as `tag = "Widgets"`.
#[get("/widgets", tag = "Widgets")]
async fn list_widgets(
Header(_key, ..): Header<XApiKey>,
) -> ApiResult<Json<Vec<Widget>>, WidgetError> {
Ok(Json(vec![]))
}
/// Multiple tags — emitted as `tags = ["Widgets", "Public"]`.
/// Inferred request body (`Json<CreateWidget>`), inferred 201
/// success from `(StatusCode, Json<T>)`, error responses folded
/// in from the `Err` half of the return.
#[post("/widgets", tags("Widgets", "Public"))]
async fn create_widget(
Json(req): Json<CreateWidget>,
) -> ApiResult<(axum::http::StatusCode, Json<Widget>), WidgetError> {
Ok((
axum::http::StatusCode::CREATED,
Json(Widget { id: 1, name: req.name }),
))
}
/// Document a header without extracting its value — the marker is
/// listed under `headers(...)` and dedupes against any concurrent
/// `Header<H>` extractor on the same handler.
#[get("/health", headers(XApiKey))]
async fn health() -> &'static str { "ok" }
/// SseStream<E, _> return is recognized by the macro and emitted as
/// 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>>> {
SseStream::new(futures::stream::iter(Vec::new()))
}
let (router, openapi) = OpenApiRouter::<()>::new()
.routes(routes!(list_widgets, create_widget, health))
.routes(routes!(stream_build))
.split_for_parts();
let api_doc = ApiDocBuilder::new()
.title("Tour")
.version("1.0.0")
.merge(openapi)
.build();
let app = router.mount_docs(api_doc, MountOpts::default());§Header form equivalence
The shortcut macros recognize two ways to declare a header on a
handler — the Header<H> extractor in the signature and the
headers(H, …) attribute. Both rely on the
DocumentedHeader
trait, which exposes the wire name as a runtime fn so the same
marker can be reused on the layer side via
HeaderParam::typed.
Both forms are interchangeable and dedupe against each other if
the same marker appears in both, so listing a header in
headers(...) while also extracting it never produces two spec
entries.
See the doxa crate-level docs for the broader design.
Attribute Macros§
- capability
- Declare a
Capablemarker type backed by aCapabilityconstant. - delete
#[delete("/path", ...)]shortcut forutoipa::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 forutoipa::path. Seegetfor the inference rules.- post
#[post("/path", ...)]shortcut forutoipa::path. Seegetfor the inference rules.- put
#[put("/path", ...)]shortcut forutoipa::path. Seegetfor the inference rules.
Derive Macros§
- ApiError
- Derive
axum::response::IntoResponseandutoipa::IntoResponsesfor an error enum from a single per-variant declaration. - SseEvent
- Derive
SseEventMetafor an enum whose variants represent the events of a Server-Sent Event stream.