A Rust code generator that creates strongly-typed structs, HTTP clients, and SSE streaming clients from OpenAPI 3.1 specifications.
We originally built this internally at GPU CLI to generate typed Rust clients for OpenAI, Anthropic, and other APIs. After battle-testing it against real-world specs with complex union types, discriminated enums, and streaming endpoints, we decided to open source it.
- OpenAPI 3.1 support — objects, arrays, enums,
oneOf,anyOf,allOf, discriminated unions - HTTP client generation — async clients with retry logic, tracing, and auth middleware
- SSE streaming clients — first-class Server-Sent Events support with reconnection
- Smart
$refresolution — handles circular references and deep nesting - Discriminator detection — auto-detects tagged unions from
oneOf/anyOfwith const properties - TOML configuration — declarative config as an alternative to the Rust API
- Snapshot testing — built-in
instasnapshot tests for generated output - Optional specta support — add
specta::Typederives for TypeScript codegen
Add to your Cargo.toml:
[dependencies]
openapi-to-rust = "0.1"Or install the CLI:
cargo install openapi-to-rustCreate openapi-to-rust.toml:
[generator]
spec_path = "openapi.json"
output_dir = "src/generated"
module_name = "api"
[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 generate:
openapi-to-rust generate --config openapi-to-rust.tomluse openapi_to_rust::{SchemaAnalyzer, CodeGenerator, GeneratorConfig};
use std::path::PathBuf;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let spec_content = std::fs::read_to_string("openapi.json")?;
let spec_value: serde_json::Value = serde_json::from_str(&spec_content)?;
let mut analyzer = SchemaAnalyzer::new(spec_value)?;
let mut analysis = analyzer.analyze()?;
let config = GeneratorConfig {
spec_path: PathBuf::from("openapi.json"),
output_dir: PathBuf::from("src/generated"),
module_name: "api".to_string(),
enable_sse_client: true,
enable_async_client: true,
..Default::default()
};
let generator = CodeGenerator::new(config);
let result = generator.generate_all(&mut analysis)?;
generator.write_files(&result)?;
println!("Generated {} files", result.files.len());
Ok(())
}The generator produces up to four files:
| File | Description |
|---|---|
types.rs |
All struct/enum definitions from OpenAPI schemas |
client.rs |
Async HTTP client with typed methods per operation |
streaming.rs |
SSE streaming client with event parsing |
mod.rs |
Module declarations |
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("your-api-key");
let request = CreateResourceRequest { /* ... */ };
let response = client.create_resource(request).await?;
Ok(())
}The generated HTTP client includes:
- All HTTP methods — GET, POST, PUT, DELETE, PATCH
- Retry with backoff — exponential backoff via
reqwest-retry(retries 429, 500, 502, 503, 504) - Distributed tracing — automatic request/response spans via
reqwest-tracing - Authentication — Bearer token, API key, or custom header auth
- Default headers — configurable per-client headers
- Middleware stack — composable request/response middleware
First-class support for Server-Sent Events endpoints:
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()
};Automatically detects and generates tagged enums from OpenAPI discriminator patterns:
# OpenAPI spec
MessageContent:
oneOf:
- $ref: '#/components/schemas/TextContent'
- $ref: '#/components/schemas/ImageContent'
discriminator:
propertyName: type
mapping:
text: '#/components/schemas/TextContent'
image: '#/components/schemas/ImageContent'Generates:
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum MessageContent {
Text(TextContent),
Image(ImageContent),
}Also handles untagged unions (anyOf without discriminator), allOf composition, extensible enums, and inline variant schemas.
[generator]
spec_path = "openapi.json" # Path to OpenAPI spec (required)
output_dir = "src/generated" # Output directory (required)
module_name = "types" # Module name (required)
[features]
enable_sse_client = true # Generate SSE streaming client
enable_async_client = true # Generate HTTP REST client
enable_specta = false # Add specta::Type derives
[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
header_name = "Authorization"
[[http_client.headers]]
name = "content-type"
value = "application/json"
[nullable_overrides]
"Response.error" = true # Override nullable detection
[type_mappings]
"DateTime" = "chrono::DateTime<chrono::Utc>"# Run all tests
cargo test
# Run snapshot tests
cargo insta test
# Review snapshot changes
cargo insta reviewThe examples/ directory contains working examples for:
- Basic type generation
- HTTP client generation
- Discriminated unions and
anyOf/oneOfpatterns allOfcomposition- Inline objects and enums
- Nullable field handling
- Recursive schemas
- TOML configuration
cargo run --example basic_generation
cargo run --example client_generation_example
cargo run --example discriminated_unions- Fork the repo
- Add your OpenAPI spec pattern to
examples/ortests/fixtures/ - Write a test using snapshot testing (
insta) - Run
cargo insta testand review the output - Submit a PR
MIT