#[oxapi]Expand description
Generate type-safe server stubs from OpenAPI specifications.
§Syntax
#[oxapi(framework, "spec_path")]
#[oxapi(framework, "spec_path", unwrap)]
#[oxapi(framework, "spec_path", ok_suffix = "Response", err_suffix = "Error")]
#[oxapi(framework, "spec_path", derive = (Debug, Clone))]
#[oxapi(framework, "spec_path", unwrap, ok_suffix = "Ok", err_suffix = "Err")]§Arguments
framework: The web framework to generate code for. Currently onlyaxumis supported.spec_path: Path to the OpenAPI specification file (JSON or YAML), relative toCargo.toml.unwrap(optional): Emit contents directly without wrapping in a module.ok_suffix(optional): Suffix for success response types. Defaults to"Response".err_suffix(optional): Suffix for error response types. Defaults to"Error".derive(optional): Default derives for generated response enums. Defaults to(Debug).
All options after the spec path can be specified in any order.
§Usage Modes
§Single Trait Mode
Apply directly to a trait to generate types in a sibling {trait_name}_types module:
#[oxapi::oxapi(axum, "spec.json")]
trait PetService<S> {
#[oxapi(map)]
fn map_routes(router: Router<S>) -> Router<S>;
#[oxapi(get, "/pet/{petId}")]
async fn get_pet(state: State<S>, pet_id: Path<_>) -> Result<GetPetResponse, GetPetError>;
}§Module Mode
Apply to a module containing one or more traits. Types are generated in a shared types submodule:
#[oxapi::oxapi(axum, "spec.json")]
mod api {
trait PetService<S> {
#[oxapi(map)]
fn map_routes(router: Router<S>) -> Router<S>;
#[oxapi(get, "/pet/{petId}")]
async fn get_pet(state: State<S>, pet_id: Path<_>);
}
trait StoreService<S> {
#[oxapi(map)]
fn map_routes(router: Router<S>) -> Router<S>;
#[oxapi(get, "/store/inventory")]
async fn get_inventory(state: State<S>);
}
}§Unwrap Mode
Use unwrap to emit contents directly without a module wrapper:
#[oxapi::oxapi(axum, "spec.json", unwrap)]
mod api {
trait PetService<S> { /* ... */ }
}
// Generates: pub mod types { ... } pub trait PetService<S> { ... }
// Instead of: mod api { pub mod types { ... } pub trait PetService<S> { ... } }§Method Attributes
§#[oxapi(map)]
Marks a method as the route mapper. Must have signature fn map_routes(router: Router<S>) -> Router<S>.
The macro fills in the body to register all routes:
#[oxapi(map)]
fn map_routes(router: Router<S>) -> Router<S>;
// Generates body: router.route("/pet/{petId}", get(Self::get_pet)).route(...)§#[oxapi(method, "path")]
Maps a trait method to an OpenAPI operation. Supported methods: get, post, put, delete, patch, head, options.
#[oxapi(get, "/pet/{petId}")]
async fn get_pet(state: State<S>, pet_id: Path<_>);
#[oxapi(post, "/pet")]
async fn add_pet(state: State<S>, body: Json<_>);§#[oxapi(spec, "endpoint_path")]
Serves the embedded OpenAPI spec at the given endpoint path. The method must be completely bare (no parameters, no return type, not async, no generics). The macro generates:
- A method that returns
&'static strcontaining the spec contents viainclude_str! - A GET route at the specified path (added to
map_routes)
This endpoint does not appear in the OpenAPI spec itself but can be used for validation purposes.
#[oxapi(spec, "/openapi.yaml")]
fn spec();
// Generates: fn spec() -> &'static str { include_str!("path/to/spec.yaml") }
// And adds: .route("/openapi.yaml", get(|| async { Self::spec() }))§Type Elision
Use _ as a type parameter to have the macro infer the correct type from the OpenAPI spec:
Path<_>→ Inferred from path parameters (single type or tuple for multiple)Query<_>→ Generated query struct{OperationId}QueryJson<_>→ Inferred from request body schemaState<S>→ Passed through unchanged (user-provided)
#[oxapi(get, "/pet/{petId}")]
async fn get_pet(
state: State<S>, // User state, unchanged
pet_id: Path<_>, // Becomes Path<i64> based on spec
query: Query<_>, // Becomes Query<GetPetQuery>
);
#[oxapi(post, "/pet")]
async fn add_pet(
state: State<S>,
body: Json<_>, // Becomes Json<Pet> based on spec
);§Custom Extractors
Parameters with explicit types (no _ elision) are passed through unchanged.
This allows adding authentication, state, or other custom extractors:
#[oxapi(post, "/items")]
async fn create_item(
state: State<S>,
claims: Jwt<AppClaims>, // Custom extractor - passed through unchanged
body: Json<_>, // Type elision - inferred from spec
);§Parameter Role Attributes
Use #[oxapi(path)], #[oxapi(query)], or #[oxapi(body)] on parameters when
the extractor name isn’t recognized (Path, Query, Json):
#[oxapi(get, "/items/{id}")]
async fn get_item(
state: State<S>,
#[oxapi(path)] id: MyPathExtractor<_>, // Infers type from path params
#[oxapi(query)] q: MyQueryExtractor<_>, // Infers type as query struct
);Note: When ANY parameter has an explicit role attribute, inference is disabled for ALL parameters. Use explicit attrs on all params that need type elision.
§Capturing Unknown Query Parameters
Use #[oxapi(query, field_name)] to capture query parameters not in the spec:
#[oxapi(get, "/search")]
async fn search(
state: State<S>,
#[oxapi(query, extras)] q: Query<_>,
);
// Generates SearchQuery with: #[serde(flatten)] pub extras: HashMap<String, String>§Generated Types
For each operation, the macro generates:
{OperationId}{ok_suffix}- Enum with success response variants (2xx status codes), default suffix isResponse{OperationId}{err_suffix}- Enum with error response variants (4xx, 5xx, default), default suffix isError{OperationId}Query- Struct for query parameters (if operation has query params){OperationId}Path- Struct for path parameters (if operation has path params)
All response enums implement axum::response::IntoResponse.
§Type Customization
§Schema Type Conversion
Replace JSON schema constructs with custom types using #[convert(...)] on the module:
#[oxapi(axum, "spec.json")]
#[convert(into = uuid::Uuid, type = "string", format = "uuid")]
#[convert(into = rust_decimal::Decimal, type = "number")]
mod api {
// All schemas with type="string", format="uuid" use uuid::Uuid
// All schemas with type="number" use rust_decimal::Decimal
}§Convert Attribute Fields
into: The replacement Rust type (required)type: JSON schema type ("string","number","integer","boolean","array","object")format: JSON schema format (e.g.,"uuid","date-time","uri")
§Schema Type Patching (Rename/Derive)
Rename schema types or add derives using struct declarations:
#[oxapi(axum, "spec.json")]
mod api {
// Rename "Veggie" from the schema to "Vegetable" in generated code
#[oxapi(Veggie)]
struct Vegetable;
// Add extra derives to a schema type
#[oxapi(Veggie)]
#[derive(schemars::JsonSchema, PartialEq, Eq, Hash)]
struct Vegetable;
}§Schema Type Replacement
Replace a schema type entirely with an existing Rust type:
#[oxapi(axum, "spec.json")]
mod api {
// Use my_networking::Ipv6Cidr instead of generating Ipv6Cidr
#[oxapi]
type Ipv6Cidr = my_networking::Ipv6Cidr;
}§Generated Type Customization
Rename or replace the auto-generated Response/Error/Query types using operation coordinates:
#[oxapi(axum, "spec.json")]
mod api {
// Rename GetPetByIdResponse to PetResponse
#[oxapi(get, "/pet/{petId}", ok)]
struct PetResponse;
// Rename GetPetByIdError to PetError
#[oxapi(get, "/pet/{petId}", err)]
struct PetError;
// Rename FindPetsByStatusQuery to PetSearchParams
#[oxapi(get, "/pet/findByStatus", query)]
struct PetSearchParams;
// Replace GetPetByIdResponse with a custom type (skips generation)
#[oxapi(get, "/pet/{petId}", ok)]
type _ = my_types::PetResponse;
// Replace GetPetByIdError with a custom type
#[oxapi(get, "/pet/{petId}", err)]
type _ = my_types::PetError;
}§Enum Variant Renaming
Rename individual status code variants within response enums using the enum syntax:
#[oxapi(axum, "spec.json")]
mod api {
// Rename the enum to PetError and customize specific variant names
#[oxapi(get, "/pet/{petId}", err)]
enum PetError {
#[oxapi(status = 401)]
Unauthorized,
#[oxapi(status = 404)]
NotFound,
}
// Generates: enum PetError { Unauthorized(...), NotFound(...), Status500(...), ... }
// instead of: enum GetPetByIdError { Status401(...), Status404(...), Status500(...), ... }
}Only status codes you specify will be renamed; others retain their default Status{code} names.
§Inline Type Naming
When a response has an inline schema (not a $ref), oxapi generates a struct for it.
The default name is {EnumName}{VariantName}. You can override this by specifying
a type name in the variant:
#[oxapi(axum, "spec.json")]
mod api {
#[oxapi(get, "/store/inventory", ok)]
enum InventoryResponse {
#[oxapi(status = 200)]
Success(Inventory), // The inline schema struct will be named "Inventory"
}
}Note: This only works for inline schemas. Using a type name with a $ref schema
will result in a compile error.
§Attribute Pass-Through
All attributes on the enum (except #[oxapi(...)]) and on variants (except #[oxapi(...)])
are passed through to the generated code. If you specify a #[derive(...)] on the enum,
it completely overrides the default derives.
#[oxapi(axum, "spec.json")]
mod api {
#[oxapi(get, "/pet/{petId}", err)]
#[derive(Debug, thiserror::Error)] // Overrides default, must include Debug if needed
enum PetError {
#[oxapi(status = 401)]
#[error("Unauthorized access")]
Unauthorized,
#[oxapi(status = 404)]
#[error("Pet not found: {0}")]
NotFound(String),
}
}§Kind Values
ok- Success response enum ({OperationId}{ok_suffix}, default:{OperationId}Response). Supports variant renames with enum syntax.err- Error response enum ({OperationId}{err_suffix}, default:{OperationId}Error). Supports variant renames with enum syntax.query- Query parameters struct ({OperationId}Query)path- Path parameters struct ({OperationId}Path)
§Complete Example
use axum::{Router, extract::{State, Path, Query, Json}};
#[oxapi::oxapi(axum, "petstore.json")]
#[convert(into = uuid::Uuid, type = "string", format = "uuid")]
mod api {
// Rename a schema type (Pet from OpenAPI becomes Animal in Rust)
#[oxapi(Pet)]
#[derive(Clone)]
struct Animal;
// Replace a generated response type
#[oxapi(get, "/pet/{petId}", err)]
type _ = crate::errors::PetNotFound;
trait PetService<S: Clone + Send + Sync + 'static> {
#[oxapi(map)]
fn map_routes(router: Router<S>) -> Router<S>;
#[oxapi(get, "/pet/{petId}")]
async fn get_pet(state: State<S>, pet_id: Path<_>);
#[oxapi(post, "/pet")]
async fn add_pet(state: State<S>, body: Json<_>);
#[oxapi(get, "/pet/findByStatus")]
async fn find_by_status(state: State<S>, query: Query<_>);
}
}
// Implement the trait
struct MyPetService;
impl api::PetService<AppState> for MyPetService {
fn map_routes(router: Router<AppState>) -> Router<AppState> {
<Self as api::PetService<AppState>>::map_routes(router)
}
async fn get_pet(
State(state): State<AppState>,
Path(pet_id): Path<i64>,
) -> Result<api::types::GetPetByIdResponse, crate::errors::PetNotFound> {
// Implementation
}
// ...
}