openapi-to-rust 0.8.0

Generate typed Rust models, HTTP/SSE clients, and Axum servers from OpenAPI 3.0/3.1 (experimental 3.2)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
# openapi-to-rust — OpenAPI generator for Rust

[![CI](https://github.com/gpu-cli/openapi-to-rust/actions/workflows/ci.yml/badge.svg)](https://github.com/gpu-cli/openapi-to-rust/actions/workflows/ci.yml)
[![Crates.io](https://img.shields.io/crates/v/openapi-to-rust.svg)](https://crates.io/crates/openapi-to-rust)
[![docs.rs](https://docs.rs/openapi-to-rust/badge.svg)](https://docs.rs/openapi-to-rust)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

`openapi-to-rust` is an OpenAPI generator for Rust that turns OpenAPI 3.0/3.1 (and experimental 3.2) specifications into strongly-typed structs, async HTTP clients, SSE streaming clients, **and opt-in Axum server scaffolding** — including for the messy, real-world specs everyone actually ships.

Read the [guides and documentation](https://openapi-to-rust.dev/) for the quickest path from an OpenAPI document to compiling Rust.

## 5-second trial

Paste your spec into the **[browser playground](https://openapi-to-rust.dev/playground)** —
the real generator compiled to WebAssembly. See the generated Rust instantly and
download it as a complete, compilable crate. Nothing is uploaded.

## 30-second trial

Install the CLI from crates.io, then generate a tiny client from a stable,
hosted fixture:

```bash
cargo install --locked openapi-to-rust
openapi-to-rust generate https://raw.githubusercontent.com/gpu-cli/openapi-to-rust/v0.8.0/tests/fixtures/operation_extraction/simple_get.json
```

This writes `src/generated/{types,client,mod}.rs` and the exact dependencies
needed to compile them in `src/generated/REQUIRED_DEPS.toml`. Use `--dry-run`
to inspect the plan without writing, and `--check` in CI to detect stale
committed output.

We originally built this internally at [GPU CLI](https://gpu-cli.sh) to generate typed Rust clients for OpenAI, Anthropic, Cloudflare, and other large APIs. After battle-testing it against real-world specs with complex union types, discriminated enums, streaming endpoints, and the occasional spec/API drift, we decided to open source it.

The repository contains **55 real-world specs**. The supported OpenAPI corpus is
54 specs (one Gitea document is Swagger 2.0 and intentionally skipped). Pull
requests compile-check the OpenAI and Anthropic production specs; a scheduled
and manually runnable CI tier checks all 54 OpenAPI specs.

Release history and breaking changes live in the [changelog](CHANGELOG.md).

## Highlights

- **OpenAPI 3.0 and 3.1, with experimental 3.2 support** — handles `type: ["X", "null"]`, `anyOf`/`oneOf`/`allOf`, discriminated unions, `const`, inline objects, and accepts paths-less specs (components-only or webhooks-only).
- **Generates clients *and* servers** — pick client calls with `[client]`, hosted operations with `[server]`, or keep the default all-operation client. Both share the same `types.rs`.
- **Typed scalars**`format: date-time``chrono::DateTime<chrono::Utc>`, `uri``url::Url`, `binary``bytes::Bytes`, `uuid``uuid::Uuid`, `byte``Vec<u8>` + base64 codec, unsigned-int formats → `u32`/`u64`. All opt-out per-format in TOML.
- **Async HTTP client** — typed methods per operation, retry/backoff via `reqwest-retry`, distributed tracing via `reqwest-tracing`, Bearer / API-key / custom auth (honored at runtime), default headers, path-template percent-encoding.
- **Axum server scaffolding** — trait per tag, status-code-typed response enum, SSE-ready `OkStream` variant, required-param HTTP 400 short-circuit at the handler boundary, combined `build_router(...)` factory for multi-tag selections.
- **SSE streaming clients** — first-class Server-Sent Events with reconnection.
- **Smart discriminated unions** — auto-detects implicit discriminators from `const` properties, falls back to `#[serde(untagged)]` when a union mixes scalar and object branches (e.g. `"auto"` *or* a tagged object).
- **Per-operation typed errors** — each operation gets its own error enum with `Status4xx(...)` typed bodies; you can match on the exact API error shape.
- **Typed `additionalProperties`** — extra keys become `BTreeMap<String, T>` instead of falling to `serde_json::Value` when the spec gives a value-type schema.
- **Constraint-as-doc**`minLength`/`maxLength`/`minimum`/`pattern` etc. are emitted as `/// Constraint: …` doc comments. **No runtime validation is added**, so generated code stays free of validator-crate dependencies.
- **TOML configuration** with overrides for spec quirks (nullable, extensible enums, type aliases).
- **Snapshot testing**`insta` snapshots for generated output.
- **Optional `specta::Type` derives** for cross-language type sharing.

## Install

Rust users with Rust 1.88 or newer can install the CLI from crates.io:

```bash
cargo install --locked openapi-to-rust
```

This compiles the CLI locally and requires a Rust toolchain. Prebuilt binaries
are not currently published. If you use the generator as a Rust library instead,
run `cargo add openapi-to-rust`.

## Try it in one command

Generate types and an async client directly from a local document or HTTPS URL:

```bash
openapi-to-rust generate openapi.yaml
# Writes src/generated/{types,client,mod}.rs and REQUIRED_DEPS.toml.
```

The async client is the default. Use `--types-only` to omit it, `--dry-run` to
analyze without writing, or `--check` in CI to fail when committed output is
stale. Remote fetches require HTTPS (except loopback development), reject URL
credentials and redirects, and enforce time and response-size limits.

To keep a reusable config:

```bash
openapi-to-rust init openapi.yaml
openapi-to-rust generate
```

## Quick start — client

`openapi-to-rust.toml`:

```toml
[generator]
spec_path = "openapi.json"
output_dir = "src/generated"
module_name = "api"
# All paths above are relative to this TOML file, not the shell's working directory.

[generator.builders]
# Add `*_builder()` when an operation has more than three optional values.
enabled = true
threshold = 3

[features]
enable_async_client = true

[http_client]
base_url = "https://api.example.com"
timeout_seconds = 30

[http_client.retry]
max_retries = 3

[http_client.auth]
type = "Bearer"
header_name = "Authorization"
```

Then:

```bash
openapi-to-rust generate --config openapi-to-rust.toml
```

### Select only the client operations you use

`[client]` is optional. Without it—or with an empty `operations` list—the HTTP client keeps every operation for backward compatibility. Selectors share the server grammar: exact `operationId`, exact `METHOD /path`, or `tag:<name>`.

```toml
[client]
operations = [
  "createResponse",
  "GET /v1/models",
  "tag:Files",
]
prune_models = true
```

`prune_models = true` also trims `types.rs`. When client and server selections coexist, the generator retains the union of schemas reachable from both scopes, including inline generated request, response, and parameter types. `[client]` is ignored when `enable_async_client = false`, and it never filters an enabled operation registry.

## Quick start — Axum server

Pick the operations you want to host. The generator emits a trait, a typed response enum, and a router factory. You implement the trait; `axum` does the rest.

```toml
[generator]
spec_path = "openai.yaml"
output_dir = "src/gen"
module_name = "openai"

[features]
enable_async_client = false        # server-only

[server]
framework = "axum"
operations = ["createResponse", "listInputItems"]
# Drop schemas not reachable from the picked operations. Safe when
# you're not also generating the HTTP client (the client would lose types).
prune_models = true
```

Discover and scaffold operations from the CLI:

```bash
# List operations in the spec (filter by tag / method / substring)
openapi-to-rust server list --tag Responses

# Add an operation to [server].operations (preserves TOML formatting)
openapi-to-rust server add createResponse
openapi-to-rust server add --all-tag Responses
openapi-to-rust server add createResponse --regenerate   # re-run codegen

# Remove
openapi-to-rust server remove createResponse
```

Then implement the trait:

```rust
use gen::server::{ResponsesApi, CreateResponseResponse, build_router, sse_response};
use gen::CreateResponse;
use axum::response::sse::Event;
use futures_util::stream;

#[derive(Clone)]
struct AppState;

#[axum::async_trait]
impl ResponsesApi for AppState {
    async fn create_response(&self, body: CreateResponse) -> CreateResponseResponse {
        if body.stream == Some(true) {
            // SSE branch — the generated `sse_response` helper takes any
            // Stream<Item = Result<Event, Infallible>> and returns the
            // exact payload the OkStream variant expects.
            CreateResponseResponse::OkStream(sse_response(stream::iter(vec![
                Ok(Event::default().event("response.created").data("{}")),
                Ok(Event::default().event("response.completed").data("{}")),
            ])))
        } else {
            // Unary branch — typed body, typed response.
            CreateResponseResponse::Ok(/* construct gen::Response */ todo!())
        }
    }
}

#[tokio::main]
async fn main() {
    let app = build_router(AppState);
    let lis = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(lis, app).await.unwrap();
}
```

Two complete examples are in the repo:

- [`examples/server-openai-responses`]examples/server-openai-responses/`createResponse` + `listInputItems` + `usage-costs` (multi-tag, body + SSE, four query params, required param 400)
- [`examples/server-anthropic-messages`]examples/server-anthropic-messages/`messages_post` with a small overlay that declares `text/event-stream` on the 200 (Anthropic's published spec omits it)

## Generated Output

| File | Description |
|------|-------------|
| `types.rs` | All struct/enum definitions from OpenAPI schemas |
| `client.rs` | Async HTTP client with typed methods per operation (when `enable_async_client`) |
| `streaming.rs` | SSE streaming **client** with event parsing (when configured) |
| `server/mod.rs` | Module re-exports for the server (when `[server]` is set) |
| `server/api.rs` | `trait <Tag>Api { async fn <op>(&self, …) -> <Op>Response; }` per tag |
| `server/errors.rs` | `enum <Op>Response { Ok(T), BadRequest(E), …, OkStream(Sse<…>) }` with `IntoResponse` |
| `server/router.rs` | Per-tag `Router` factory; combined `build_router<…>(…)` for multi-tag selections |
| `mod.rs` | Module declarations + re-exports |
| `REQUIRED_DEPS.toml` | Complete direct dependencies and crate features for the exact generated modes and selected operations — append or merge into the consuming `Cargo.toml` |

### Generated client usage

```rust
use crate::generated::client::HttpClient;
use crate::generated::types::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = HttpClient::new()
        .with_base_url("https://api.example.com")
        .with_api_key(std::env::var("API_KEY")?);

    let req = CreateResourceRequest { /* … */ };
    let resource = client.create_resource(req).await?;
    Ok(())
}
```

## What the generated types look like

A tour of patterns the generator emits, from real outputs.

### Typed scalars

```rust
// format: date-time → chrono::DateTime<chrono::Utc>
pub created_at: chrono::DateTime<chrono::Utc>,
pub archived_at: Option<chrono::DateTime<chrono::Utc>>,

// format: uri → url::Url
pub url: url::Url,
pub callback_url: Option<url::Url>,

// format: binary (multipart) → bytes::Bytes
Binary(bytes::Bytes),

// format: uuid → uuid::Uuid
pub request_id: uuid::Uuid,
```

### Typed `additionalProperties`

```rust
pub additional_properties: std::collections::BTreeMap<String, f64>,    // usage maps
pub additional_properties: std::collections::BTreeMap<String, String>, // labels
```

### Constraints as doc comments

```rust
///Constraint: minLength=1, maxLength=64, pattern=`^[a-zA-Z0-9_-]{1,64}$`
pub custom_id: String,

///Constraint: minimum=0, maximum=1
pub temperature: Option<f64>,
```

> **No runtime validation is generated.** The generator never adds the `validator` crate or `#[validate(...)]` attributes — constraints are documentation only. Validate at boundaries you control.

### Discriminated unions (tagged enums)

```rust
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum MessageContent {
    Text(TextContent),
    Image(ImageContent),
}
```

### Hybrid string-or-object unions

When an `anyOf`/`oneOf` mixes a string-enum branch with tagged-object branches (a common OpenAI pattern), the generator emits an **untagged** enum so both forms deserialize:

```rust
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]                                   // not #[serde(tag="type")]
pub enum ToolChoiceParam {
    ToolChoiceOptions(ToolChoiceOptions),            // string-enum: "none"|"auto"|"required"
    ToolChoiceFunction(ToolChoiceFunction),
    ToolChoiceMCP(ToolChoiceMCP),
    // …
}
```

### Extensible enums (with `Custom(String)` fallback)

When the spec declares an `anyOf` of `const` strings plus an open `string` branch (or you opt in via `[extensible_enums]`, see below), the enum has a `Custom(String)` arm so unknown values still deserialize:

```rust
pub enum Model {
    ClaudeSonnet46,
    ClaudeOpus46,
    ClaudeHaiku45,
    Claude3Haiku20240307,
    Custom(String),         // ← anything not in the known set
}
```

### Per-operation typed errors

Each operation has its own error enum that wraps the typed body of each documented response code:

```rust
let resp = client.create_response(req).await;
match resp {
    Ok(body) => { /* … */ }
    Err(ApiOpError::Api(err)) => match err.typed {
        Some(CreateResponseApiError::Status4xx(typed)) => {
            // typed is the spec's typed 4xx body, e.g. ResponseInfo {
            //   code: 10042,
            //   message: "Please enable R2 through the Cloudflare Dashboard.",
            // }
        }
        _ => eprintln!("raw body: {}", err.body),
    },
    Err(ApiOpError::Transport(e)) => eprintln!("transport: {}", e),
}
```

## Streaming (SSE)

```rust
use openapi_to_rust::streaming::*;

let streaming_config = StreamingConfig {
    endpoints: vec![StreamingEndpoint {
        operation_id: "createChatCompletion".to_string(),
        path: "chat/completions".to_string(),
        stream_parameter: "stream".to_string(),
        event_union_type: "ChatCompletionStreamEvent".to_string(),
        event_flow: EventFlow::StartDeltaStop {
            start_events: vec!["response.created".to_string()],
            delta_events: vec!["response.output_text.delta".to_string()],
            stop_events: vec!["response.completed".to_string()],
        },
        ..Default::default()
    }],
    reconnection_config: Some(ReconnectionConfig {
        max_retries: 5,
        initial_delay_ms: 500,
        max_delay_ms: 16000,
        backoff_multiplier: 2.0,
    }),
    ..Default::default()
};
```

Generated event types are tagged enums you can match on directly:

```rust
match serde_json::from_str::<ResponseStreamEvent>(&data)? {
    ResponseStreamEvent::TextDelta(d)  => out.push_str(&d.delta),
    ResponseStreamEvent::Completed(_)  => break,
    _                                   => {}
}
```

The generator also **auto-detects** streaming endpoints: any response declaring `content: text/event-stream` flips `supports_streaming = true` automatically. Explicit `[[streaming.endpoints]]` config still wins when present.

## Spec-quirk overrides

Real specs lie. These TOML knobs let you patch quirks without forking the spec.

### `schema_extensions` — overlay JSON/YAML fragments onto the spec

A list of files whose top-level objects are deep-merged into the main spec before analysis. Use it to add a `text/event-stream` content entry, add a missing operation, or tweak a schema without forking the upstream spec. Example: the Anthropic server example overlays `sse-overlay.json` so `messages_post` gets an SSE response variant.

```toml
[generator]
# Relative paths are resolved from the directory containing this TOML file.
schema_extensions = ["sse-overlay.json"]
```

### `nullable_overrides` — force a field to `Option<T>`

When a spec marks a field as required + non-nullable but the API actually returns `null`. Format: `"SchemaName.fieldName" = true`.

```toml
[nullable_overrides]
# OpenAI's spec uses a bare $ref for `error`; the API actually returns null on success.
"Response.error" = true
```

### `extensible_enums` — force a closed enum to accept unknown values

When the spec declares a fixed enum but the API actually returns values outside the set (real-world drift). Renders the enum with a `Custom(String)` fallback variant. Accepts either the raw spec name or the rendered Rust type name.

```toml
[extensible_enums]
# CF spec declares lowercase ["apac", ..., "wnam"] but the API returns "WNAM".
"R2BucketLocation" = true
# OpenAI spec declares ["in-memory", "24h"] but the API returns "in_memory".
"ModelResponsePropertiesPromptCacheRetention" = true
```

### `type_mappings` — override a primitive's Rust type

```toml
[type_mappings]
"DateTime" = "chrono::DateTime<chrono::Utc>"
```

### `[generator.types]` — typed-scalar strategy per format

Opt out of any individual typed scalar (e.g. fall back to `String` for date-times if you don't want `chrono`):

```toml
[generator.types]
date_time = "string"        # default: "chrono"
uri       = "string"        # default: "url"
binary    = "string"        # default: "bytes"
```

The CLI also supports `--types-conservative`, which collapses every typed scalar to `String`/`i64`/etc. Use it when you want zero optional-crate dependencies.

### `[generator.builders]` — additive operation builders

Large operations can keep their existing flat async method and also expose a `*_builder()` entry point:

```toml
[generator.builders]
enabled = true
threshold = 3
```

A builder is emitted when its optional query/header/body values and reachable optional request-body fields exceed `threshold`. Required path, query, header, and request-model values stay explicit. Setters own their values, and `.send().await` delegates to the existing flat method, so request serialization and response errors stay identical. Builder generation is disabled by default to preserve existing generated call sites.

## OpenAPI 3.1 / 3.2 support

The generator accepts 3.0.x and 3.1.x specs; 3.2.x parses with an
`experimental` warning. Unknown fields are retained for compatibility, but an
unrecognized field may be ignored by analysis and code generation. Add a
focused fixture when relying on a less-common OpenAPI or JSON Schema keyword.

**3.1 (JSON Schema 2020-12) keywords now modeled and read from typed fields:**

| Keyword group | Status |
|---|---|
| `type` as array (e.g. `["string", "null"]`) | typed + used for nullability |
| `prefixItems`, `unevaluatedItems`, `contains` / `minContains` / `maxContains` | typed |
| `patternProperties`, `propertyNames`, `unevaluatedProperties` | typed |
| `dependentRequired`, `dependentSchemas`, `if` / `then` / `else` | typed |
| `contentEncoding`, `contentMediaType`, `contentSchema` | typed |
| `$dynamicRef`, `$dynamicAnchor`, `$defs`, `$id`, `$schema`, `$comment` | typed (anchor-scope resolution is a follow-up) |
| Path Item `$ref` resolution | resolved at analysis time |
| Webhooks (`webhooks:`) | ingested as operations |
| `examples`, `example`, `title`, `deprecated`, `readOnly`/`writeOnly` | typed |

**3.2 deltas (experimental):**

| Delta | Status |
|---|---|
| `query` HTTP method + `PathItem.additionalOperations` | parses + emits client methods via `reqwest::Method::from_bytes(...)` |
| OAuth `deviceAuthorization` flow + `oauth2MetadataUrl` | typed |
| `Server.name`, `Tag.parent`/`kind`/`summary` | typed |
| `Discriminator.defaultMapping` | typed (captured; `_Other(Value)` fallback emission is a follow-up) |
| `MediaType.itemSchema`, `prefixEncoding`, `itemEncoding` | typed |
| `mediaTypes` in Components | typed |

**Modeled Components family:** `Server`, `ServerVariable`, `SecurityScheme` (apiKey / http / mutualTLS / oauth2 / openIdConnect with all flows), `OAuthFlows`, `Encoding`, `Header`, `Example`, `Link`, `Callback`, `Tag`, `ExternalDocs`, `Discriminator`. Modeling does not imply runtime support for every security or linking behavior; for example, mutual TLS credentials are not configured by generated clients.

The fixture harness under `tests/conformance/` currently gates lossless L0
parsing; its later L1–L5 markers are tracked but deferred. The vendored JSON
Schema Test Suite runner enforces a no-regression ceiling for parse failures
and lossless round trips; it does not yet enforce semantic, feature-level pass
thresholds.

## CLI

```bash
# Direct mode: client by default; use --types-only to omit it.
openapi-to-rust generate openapi.yaml
openapi-to-rust generate https://example.com/openapi.json --dry-run --json
openapi-to-rust generate openapi.yaml --check --quiet

# Create a starter config, then generate with the config defaults.
openapi-to-rust init openapi.yaml
openapi-to-rust generate

# Explicit config mode.
openapi-to-rust generate --config openapi-to-rust.toml
openapi-to-rust generate --config openapi-to-rust.toml --types-conservative
openapi-to-rust validate --config openapi-to-rust.toml

# Server scope management — preserves TOML formatting (toml_edit)
openapi-to-rust server list                    # all operations
openapi-to-rust server list --tag Responses    # filter by tag
openapi-to-rust server list --method POST --grep response
openapi-to-rust server list --json             # JSON output
openapi-to-rust server add createResponse                 # add one
openapi-to-rust server add --all-tag Responses            # expand a tag
openapi-to-rust server add createResponse --dry-run       # preview
openapi-to-rust server add createResponse --regenerate    # add + regenerate
openapi-to-rust server remove createResponse              # remove
```

Selectors are forgiving — `operationId`, `METHOD /path`, or `tag:<name>`. Typos surface Levenshtein-based "Did you mean …?" suggestions.

## TOML reference

```toml
[generator]
spec_path = "openapi.json"              # required
output_dir = "src/generated"            # required
module_name = "types"                   # informational label, not a directory
schema_extensions = []                  # optional list of JSON/YAML overlays merged into the spec

[features]
enable_sse_client = false               # generate SSE streaming client (requires [[streaming.endpoints]])
enable_async_client = true              # generate HTTP REST client
enable_specta = false                   # add specta::Type derives
enable_registry = false                 # generate static operation registry (CLI/proxy routing)
registry_only = false                   # only generate the registry (skip types/client/streaming)

[http_client]
base_url = "https://api.example.com"
timeout_seconds = 30                    # 1-3600

[http_client.retry]
max_retries = 3                         # 0-10
initial_delay_ms = 500                  # 100-10000
max_delay_ms = 16000                    # 1000-300000

[http_client.tracing]
enabled = true

[http_client.auth]
type = "Bearer"                         # Bearer | ApiKey | Custom (honored at runtime)
header_name = "Authorization"

[[http_client.headers]]
name  = "content-type"
value = "application/json"

[[streaming.endpoints]]
operation_id     = "createChatCompletion"
path             = "chat/completions"
http_method      = "POST"
stream_parameter = "stream"
event_union_type = "ChatCompletionStreamEvent"
content_type     = "text/event-stream"

[streaming.endpoints.event_flow]
type          = "StartDeltaStop"        # or "Continuous"
start_events  = ["response.created"]
delta_events  = ["response.output_text.delta"]
stop_events   = ["response.completed"]

[server]
framework  = "axum"                     # only axum supported today
operations = [                          # selectors: operationId | "METHOD /path" | "tag:<name>"
  "createResponse",
  "POST /v1/messages",
  "tag:Responses",
]
prune_models = false                    # drop schemas outside the combined selected
                                        # client/server operation closure

[client]
operations = [                          # absent or empty means every client operation
  "createResponse",
  "GET /v1/models",
  "tag:Files",
]
prune_models = false                    # opt in to the same union model pruning

[nullable_overrides]
"Response.error" = true                 # see "Spec-quirk overrides" above

[extensible_enums]
"R2BucketLocation" = true               # see "Spec-quirk overrides" above

[type_mappings]
"DateTime" = "chrono::DateTime<chrono::Utc>"

[generator.types]
date_time = "chrono"                     # chrono (default) | time | string
uri       = "url"                        # url     (default) | string
binary    = "bytes"                      # bytes   (default) | vec_u8 | string
uuid      = "uuid"                       # uuid    (default) | string
byte      = "base64"                     # base64 (default) | base64_url_unpadded | vec_u8 | string
unsigned  = true                         # uint32/uint64 -> u32/u64

[generator.types.shape]
additional_properties_typed = true
```

## Testing

```bash
cargo test                # unit + integration tests
cargo insta test          # snapshot tests
cargo insta review        # review snapshot diffs
scripts/spec-compile.sh   # generate + cargo-check every spec in specs/ (full corpus)
```

The compile tiers are intentionally different:

- Every pull request and push to `main` generates all 54 supported OpenAPI
  specs, then compile-checks the Anthropic and OpenAI production specs against
  each generated `REQUIRED_DEPS.toml`.
- Weekly scheduled CI and manual workflow runs compile-check all 54 supported
  OpenAPI specs. The bundled Gitea Swagger 2.0 document is reported as skipped.
- Local `scripts/spec-compile.sh` runs the same full tier; pass spec names as
  arguments for a smaller targeted run.

## Examples

```bash
# Library / generator examples
cargo run --example basic_generation
cargo run --example client_generation_example
cargo run --example discriminated_unions
cargo run --example anyof_unions
cargo run --example allof_composition
cargo run --example openai_patterns
cargo run --example toml_config_example

# End-to-end Axum server examples (generate + run)
cargo run -p openapi-to-rust -- generate --config examples/server-openai-responses/openapi-to-rust.toml
cargo run --manifest-path examples/server-openai-responses/Cargo.toml

cargo run -p openapi-to-rust -- generate --config examples/server-anthropic-messages/openapi-to-rust.toml
cargo run --manifest-path examples/server-anthropic-messages/Cargo.toml
```

## Versioning

Until 1.0.0, a minor version may change generated Rust APIs when correcting
output that was wrong or incomplete on the wire. Regenerating lets the compiler
identify affected call sites. See the [changelog](CHANGELOG.md) for release
history and migration notes.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, targeted test commands,
snapshot guidance, and the generated-API compatibility checklist. Questions
belong in [GitHub Discussions](https://github.com/gpu-cli/openapi-to-rust/discussions);
bugs and concrete feature requests use the repository issue forms.

By participating, you agree to the [Code of Conduct](CODE_OF_CONDUCT.md).
Security reports follow [SECURITY.md](SECURITY.md); please do not file a public
issue for a suspected vulnerability.

## License

MIT