Skip to content

ndrsg/openapi-trait

Repository files navigation

openapi-trait

Build status Crates.io Documentation

A Rust proc-macro attribute that reads an OpenAPI specification file at compile time and generates typed Rust traits from it, so you can implement your API server or define a transport-agnostic client contract with full type safety and no boilerplate.

#[openapi_trait::axum("openapi/petstore.yaml")]
pub mod petstore {}

use petstore::PetstoreApi as _;

#[derive(Clone)]
struct AppState;

#[derive(Clone)]
struct MyServer;

impl petstore::PetstoreApi<AppState> for MyServer {
    type Error = std::convert::Infallible;

    async fn get_pet_by_id(
        &self,
        req: petstore::GetPetByIdRequest,
        _state: axum::extract::State<AppState>,
        _headers: axum::http::HeaderMap,
    ) -> Result<petstore::GetPetByIdResponse, Self::Error> {
        Ok(petstore::GetPetByIdResponse::Status200(petstore::Pet {
            id: Some(req.pet_id),
            name: "doggie".into(),
            photo_urls: vec![],
            category: None,
            tags: None,
            status: None,
        }))
    }
}

// Wire up an axum router.
let app = MyServer.router().with_state(AppState);
#[openapi_trait::client("openapi/petstore.yaml")]
pub mod petstore {}

struct MyClient;

impl petstore::PetstoreClient for MyClient {
    type Error = std::convert::Infallible;

    async fn get_pet_by_id(
        &self,
        req: petstore::GetPetByIdRequest,
    ) -> Result<petstore::GetPetByIdResponse, Self::Error> {
        Ok(petstore::GetPetByIdResponse::Status200(petstore::Pet {
            id: Some(req.pet_id),
            name: "doggie".into(),
            photo_urls: vec![],
            category: None,
            tags: None,
            status: None,
        }))
    }
}

What gets generated

For every OpenAPI spec the macro emits inside the target module:

Generated item Source
Structs with serde derives components/schemas
{OperationId}Request structs Path, query, header params + request body per operation
Per-operation {OperationId}Response enums HTTP status codes per operation
impl axum::response::IntoResponse For every response enum generated by openapi_trait::axum
{ModName}Api<S = ()> trait One method per operationId for server implementations
{ModName}Client trait One method per operationId for transport-agnostic client implementations
router method on the trait Wires all operations to an axum::Router when using openapi_trait::axum

Crates

Crate Purpose
openapi-trait Main entry point — add this to your Cargo.toml
openapi-trait-axum Axum proc-macro — not for direct use
openapi-trait-client Client proc-macro and ReqwestClient derive — not for direct use
openapi-trait-shared Framework-agnostic codegen helpers — not for direct use

Usage

Add to Cargo.toml:

[dependencies]
openapi-trait = "0.1"

Then apply the macro to a mod block:

#[openapi_trait::axum("openapi/petstore.yaml")]
pub mod petstore {}

Or generate a transport-agnostic client trait:

#[openapi_trait::client("openapi/petstore.yaml")]
pub mod petstore {}

The path is resolved relative to the crate root (CARGO_MANIFEST_DIR). The file is tracked by Cargo — the crate recompiles automatically when the spec changes.

The generated trait name comes from the module name, so mod petstore {} produces petstore::PetstoreApi and petstore::PetstoreClient.

Reqwest client support

openapi-trait enables the reqwest-client feature by default. That adds:

  • #[derive(openapi_trait::ReqwestClient)] for carrier structs that hold a reqwest::Client
  • A blanket implementation of the generated {ModName}Client trait for any type implementing openapi_trait::ReqwestClientCore
  • Re-exports of reqwest and percent_encoding for generated client code
#[openapi_trait::client("openapi/petstore.yaml")]
pub mod petstore {}

#[derive(Clone, openapi_trait::ReqwestClient)]
struct PetstoreClient {
    #[openapi_trait(client)]
    http: openapi_trait::reqwest::Client,
    #[openapi_trait(base_url)]
    endpoint: String,
}

Disable default features if you only want the transport-agnostic trait:

[dependencies]
openapi-trait = { version = "0.1", default-features = false }

OpenAPI support

Feature Status
components/schemas → structs
Path parameters
Query parameters (including string enums)
Header parameters
Request bodies (JSON)
Response enums per operation
allOf / anyOf / oneOf Partial — falls back to serde_json::Value
Security schemes Not planned for v0.1