# openapi-trait
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.
```rust
#[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);
```
```rust
#[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:
| 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
| [`openapi-trait`](openapi-trait/) | Main entry point — add this to your `Cargo.toml` |
| [`openapi-trait-axum`](openapi-trait-axum/) | Axum proc-macro — not for direct use |
| [`openapi-trait-client`](openapi-trait-client/) | Client proc-macro and `ReqwestClient` derive — not for direct use |
| [`openapi-trait-shared`](openapi-trait-shared/) | Framework-agnostic codegen helpers — not for direct use |
## Usage
Add to `Cargo.toml`:
```toml
[dependencies]
openapi-trait = "0.1"
```
Then apply the macro to a `mod` block:
```rust
#[openapi_trait::axum("openapi/petstore.yaml")]
pub mod petstore {}
```
Or generate a transport-agnostic client trait:
```rust
#[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
```rust
#[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:
```toml
[dependencies]
openapi-trait = { version = "0.1", default-features = false }
```
## OpenAPI support
| `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 |