Skip to main content

oxapi

Attribute Macro oxapi 

Source
#[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 only axum is supported.
  • spec_path: Path to the OpenAPI specification file (JSON or YAML), relative to Cargo.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 str containing the spec contents via include_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}Query
  • Json<_> → Inferred from request body schema
  • State<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 is Response
  • {OperationId}{err_suffix} - Enum with error response variants (4xx, 5xx, default), default suffix is Error
  • {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
    }
    // ...
}