Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Generation Options

Use generation options to choose the output layout, select operations, and control the generated API’s names and visibility. The Flag Summary lists the defaults and accepted values.

Commands run from a checkout of this repository. If you installed the tool, replace cargo run -- with oas3-gen . Use the path to your own document in place of spec.json or petstore.json . Unless stated otherwise, examples refer to fixtures/petstore.json ; Rust excerpts may omit imports, attributes, and unrelated definitions.

Generation Modes

The positional mode argument selects the kind of output. Choose types when you need data types, client or client-mod when you want to call an API, and server-mod when you want to implement one:

cargo run -- generate <MODE> -i spec.json -o <OUTPUT>

types

The types mode writes type definitions to a single file. Let’s generate the petstore’s types:

cargo run -- generate types -i petstore.json -o types.rs

Among others, types.rs now contains the PetStatus enum and the Pet struct:

/// Where the pet is in the adoption process
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, oas3_gen_support::Default)]
pub enum PetStatus {
    #[serde(rename = "available")]
    #[default]
    Available,
    #[serde(rename = "pending")]
    Pending,
    #[serde(rename = "adopted")]
    Adopted,
}

/// A pet in the store's care.
#[derive(Debug, Clone, PartialEq, Deserialize, oas3_gen_support::Default)]
pub struct Pet {
    /// Server-assigned identifier
    pub id: uuid::Uuid,
    pub name: String,
    /// Kinds of pets the store cares for
    pub species: Species,
    /// Where the pet is in the adoption process
    pub status: Option<PetStatus>,
    /* ... */
}

The required properties id , name , and species become plain fields, and an optional property such as status becomes an Option . The derives follow how the API uses each type. Pet appears only in responses, so it derives Deserialize but not Serialize . NewPet , which the API only accepts, derives Serialize and Validate instead. Server output reverses those directions.

client

The client mode writes types and a reqwest client to one file. See Choosing the Output Layout for an example.

client-mod

The client-mod mode writes types.rs , client.rs , and mod.rs to a directory. See Client Generation for how to generate and use it.

server-mod

The server-mod mode writes types.rs , server.rs , and mod.rs to a directory. See Server Generation for how to implement the trait and use the generated Axum router.

Workspace Crate Output

Use --workspace when you want the output to have its own Cargo.toml and src/ directory. You can build that output as a crate or include it in a Cargo workspace. The flag is available with client-mod and server-mod ; using it with types or client produces a generation error.

-w, --workspace

For example, let’s generate a client crate in petstore-api :

cargo run -- generate client-mod -i petstore.json -o petstore-api --workspace

The generated directory has this structure:

petstore-api/
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── types.rs
    └── client.rs

The src/lib.rs file is the library root, so you can build petstore-api as a crate. The type and client or server files contain the same code as the corresponding module output.

Crate Name

The package takes its name from the final component of the output directory, so -o petstore-api produces name = "petstore-api" and a petstore_api library target. Names are transliterated to ASCII and converted to kebab-case: -o "output/Pet Store" yields pet-store , -o café-api yields cafe-api , and -o petstore_v2 yields petstore-v2 . The library target of petstore-v2 is still petstore_v2 , because Cargo maps dashes to underscores.

Generation fails when no valid name can be derived, for example when the directory name is empty after sanitization or starts with a digit.

Generated Manifest

The manifest declares the package and the dependencies its generated code uses. The command above writes this manifest for the petstore client, shown here without its banner; the dependency list for your API may differ:

[package]
name = "petstore-api"
version = "0.0.0"
edition = "2024"
rust-version = "1.89"
description = "Rust client generated from the Petstore OpenAPI document"

[dependencies]
anyhow = "1.0"
chrono = { version = "0.4.42", default-features = false, features = ["std", "clock", "serde"] }
http = "1.5"
indexmap = { version = "2.14", features = ["serde"] }
oas3-gen-support = "0.29.0"
regex = "1.13"
reqwest = { version = "0.13", default-features = false, features = ["form", "json", "multipart", "http2", "native-tls", "query", "stream"] }
reqwest_cookie_store = "0.10"
secrecy = "0.10"
serde = { version = "1.0", features = ["derive"] }
serde_html_form = { version = "0.4", default-features = false, features = ["ser", "std"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
serde_with = { version = "3.24", features = ["base64", "chrono"] }
uuid = { version = "1.26", features = ["serde"] }
validator = { version = "0.21", features = ["derive", "indexmap"] }

The description comes from the document’s info.title , Petstore . Adding --enable-builders adds a bon entry. Generating the petstore server with server-mod instead leaves out reqwest , reqwest_cookie_store , and serde_html_form , and adds the Axum crates:

axum = { version = "0.8.5", features = ["multipart"] }
axum-extra = { version = "0.12", features = ["cookie", "query"] }

The generator rewrites Cargo.toml on every run and adds a banner identifying it as generated output. Put code that needs its own dependencies in a separate crate so that regenerating the API doesn’t overwrite your changes.

The package version starts at 0.0.0 . Pass --module-version to set a version for the generated crate:

cargo run -- generate client-mod -i petstore.json -o petstore-api --workspace --module-version 1.0.0

The --module-version flag requires --workspace , because that’s the mode that creates the manifest.

Dependencies

Each generator release contains the dependency version requirements it writes to the manifest. It doesn’t read them from your project’s environment. The requirement for oas3-gen-support uses the generator’s version. Cargo resolves these requirements when you build the generated crate.

The manifest declares crates referenced by the generated source. For example, a specification without date or time types doesn’t need a chrono entry, and server output doesn’t need the client’s reqwest dependency. The following table describes the dependencies you may see:

CrateDeclared when the generated code
anyhowreturns anyhow::Result from client or server methods
axumdefines a server-mod router and extractors, with the multipart feature
axum-extrareads query parameters or cookie API keys on the server, with the cookie and query features
bonderives builders (--enable-builders)
chronomaps date, date-time, or time formats
futuresreturns or sends a server’s text/event-stream as a BoxStream
httpemits header constants or HeaderMap conversions
indexmapemits IndexMap/IndexSet collection types
oas3-gen-supportuses runtime derives, diagnostics, multipleOf checks, or reads event streams on the client
regexemits pattern validation constants
reqwestperforms client-mod HTTP calls; the form feature sends application/x-www-form-urlencoded bodies
reqwest_cookie_storesends cookie API keys or encodes cookie parameters from the client
secrecystores API keys and bearer tokens
serdederives Serialize/Deserialize
serde_html_formserializes query parameters on the client, with only its ser and std features
serde_jsonhandles freeform serde_json::Value payloads
serde_withapplies serde_as conversions
uuidmaps the uuid format
validatorderives Validate

serde_json gains the preserve_order feature under the default collection policy so that freeform JSON objects keep their key order. With --no-ordered-collections the feature is omitted. Likewise, validator gains its indexmap feature when the generated code also uses indexmap . The feature checks the length of an IndexSet or IndexMap field, such as the maxItems limit on the tags of NewPet .

Using the Crate

The manifest declares a plain [package] with no [workspace] table, so the crate builds standalone and can also be added to an existing workspace:

[workspace]
members = ["crates/*", "petstore-api"]

To use the generated crate from another crate, add a path dependency to that crate’s Cargo.toml :

[dependencies]
petstore-api = { path = "../petstore-api" }

Keep the default --visibility public for a crate other code depends on. crate and file visibility restrict the re-exports in lib.rs , so other crates can’t access those re-exported items.

Operation Filtering

You may only need a few operations from a large API. Use --only to select those operations, or --exclude to leave specific operations out. You can use one of these flags at a time.

--only <id1,id2,...>
--exclude <id1,id2,...>

--only

Pass the normalized snake_case operation IDs, separated by commas. Filtering uses these IDs before any --fn-name overrides. For example, the petstore’s listPets and createPet operations use list_pets and create_pet in the filter:

cargo run -- generate client-mod -i petstore.json -o output/ \
  --only list_pets,create_pet

The generated client keeps list_pets() and create_pet() and omits the other operations. With server-mod , the same selection keeps those two trait methods and their routes. Types needed by the selected methods are still included, as we’ll see in Schema Dependency Resolution .

--exclude

Use --exclude when you want most of an API but don’t need a particular operation. For example, to leave out the image upload operation, run:

cargo run -- generate client-mod -i petstore.json -o output/ \
  --exclude upload_pet_image

The client keeps the petstore’s other operations, including list_pets() and create_pet() , and omits upload_pet_image() . In server-mod mode, the flag omits that operation’s trait method and route.

A callback follows the operation that declares it. The petstore’s adopt_pet operation declares the statusChanged callback, which server-mod generates as adopt_pet_status_changed . In server-mod , --only adopt_pet keeps that callback’s route and trait method, and --exclude adopt_pet removes them.

You can also name a callback by its own normalized snake_case ID. --only adopt_pet_status_changed keeps only the callback, and --exclude adopt_pet_status_changed removes only the callback while adopt_pet stays.

Schema Dependency Resolution

Filtering an operation also filters the types needed to use it. The generator follows dependencies through the schemas so that a selected operation has the types it needs:

  1. It collects schemas used by the selected operations’ parameters, request bodies, and responses.
  2. It follows references from those schemas to other schemas.
  3. It generates the resulting set of types.

For example, the petstore’s listPets returns Pets , an array of Pet values, and an Error for its default response. Selecting only list_pets also generates the schemas Pet refers to: Species , PetStatus , the Health union with its Allergies and Diet branches, PetVaccinations , and VaccineRecord . The operation doesn’t refer to any of them directly.

Schema Filtering

By default, the generator includes schemas used by the selected operations and the schemas they depend on. Pass --all-schemas when you also need types that no operation references.

--all-schemas

Default (Reachability Filtering)

This example uses a different document. Suppose a specification defines Pet , Category , Store , and Inventory , but its operations only use Pet and Category . Generate types with the default options:

cargo run -- generate types -i spec.json -o types.rs

The output contains Pet and Category . The generator skips Store and Inventory because no selected operation needs them. With --verbose , it reports how many of these orphaned schemas it skipped.

With --all-schemas

To include those unused schemas too, add the flag:

cargo run -- generate types -i spec.json -o types.rs --all-schemas

The output now contains all four types: Pet , Category , Store , and Inventory .

Combining with Operation Filtering

You can’t combine --all-schemas with --only or --exclude in one command. If you need both a complete set of types and a filtered client, generate two outputs:

cargo run -- generate types -i spec.json -o types.rs --all-schemas
cargo run -- generate client -i spec.json -o client.rs --only list_pets

The first file contains all schemas. The second contains the filtered client and its own required types; these commands don’t make the client import types from the first file.

API Name Override

Use --api-name to name the client struct in client or client-mod mode, or the server trait in server-mod mode. The generator converts the supplied name to PascalCase .

--api-name <NAME>

By default, the client name comes from the specification’s info.title . For example, the petstore’s title, "Petstore" , becomes PetstoreClient ; an empty title produces ApiClient . The default server trait name is ApiServer . To choose PetStoreClient for the client, run:

cargo run -- generate client-mod -i petstore.json -o output/ --api-name PetStoreClient

The client struct and its implementation now use PetStoreClient . In server mode, choose a trait name with the same flag:

cargo run -- generate server-mod -i petstore.json -o output/ --api-name PetStoreApi

The server trait is named PetStoreApi , and the generated router uses that name in its trait bound. Changing the API name doesn’t rename the operations; use --fn-name for those.

Function Name Overrides

Use --fn-name to choose the generated client or server method name for an operation. The key is its operationId from the specification; the generator also accepts the normalized operation ID shown by oas3-gen list operations .

--fn-name <ID=NAME>

The generator converts your method name to snake_case and uses it to derive the request type’s name. For example, renaming listPets to fetch_all_pets also produces FetchAllPetsRequest . Repeat the flag to rename more than one operation:

cargo run -- generate client-mod -i petstore.json -o output/ \
  --fn-name listPets=fetch_all_pets \
  --fn-name showPetById=get_pet

The override changes both names together:

Operation IDMethod NameRequest Type
listPetsfetch_all_petsFetchAllPetsRequest
showPetByIdget_petGetPetRequest

Use the new names when calling the client or implementing the server trait.

Visibility

Use --visibility to choose where generated items can be accessed. The default, public , makes them available to other crates. Choose crate when the generated API is an internal part of your application.

-C, --visibility <LEVEL>
ValueModifierUse Case
public (default)pubLibrary distribution
cratepub(crate)Internal crate types
filepub(super)Items used only inside the generated module

Let’s compare the three values on two petstore types: the Pet struct and the ChatModelChoice union, which has helper methods .

Example: --visibility public

With public visibility, the types, fields, and methods use pub :

pub struct Pet {
    pub id: uuid::Uuid,
    pub name: String,
    /* ... */
}

pub enum ChatModelChoice {
    ChatModel(ChatModel),
    String(String),
}

impl ChatModelChoice {
    pub fn chat10() -> Self {
        Self::ChatModel(ChatModel::Chat10)
    }
    /* ... */
}

Example: --visibility crate

With crate visibility, those items use pub(crate) :

pub(crate) struct Pet {
    pub(crate) id: uuid::Uuid,
    pub(crate) name: String,
    /* ... */
}

pub(crate) enum ChatModelChoice {
    ChatModel(ChatModel),
    String(String),
}

impl ChatModelChoice {
    pub(crate) fn chat10() -> Self {
        Self::ChatModel(ChatModel::Chat10)
    }
    /* ... */
}

Example: --visibility file

With file visibility, those items use pub(super) :

pub(super) struct Pet {
    pub(super) id: uuid::Uuid,
    pub(super) name: String,
    /* ... */
}

pub(super) enum ChatModelChoice {
    ChatModel(ChatModel),
    String(String),
}

impl ChatModelChoice {
    pub(super) fn chat10() -> Self {
        Self::ChatModel(ChatModel::Chat10)
    }
    /* ... */
}

In module output, pub(super) lets the generated files use each other’s items: client.rs or server.rs can reach the types in types.rs . The module’s mod.rs or lib.rs doesn’t re-export anything, so the items stay inside the generated module.

Header Emission

Header name constants use http::HeaderName, so you can refer to headers without repeating their string names. By default, the generator creates constants for request parameters and response headers used by the selected operations. Use --all-headers to include header parameters from components/parameters even when no operation references them.

Headers that the http crate already names are the exception. The http::header module declares constants for standard headers such as Content-Type , Location , and Authorization . The generator doesn’t declare its own constant for any of them; generated code refers to http ’s constant, such as http::header::LOCATION , instead.

OpenAPI says to ignore header parameters named Accept , Content-Type , or Authorization , so the generator leaves them out of request types. The specification describes those headers elsewhere: media types cover Accept and Content-Type , and security schemes cover Authorization . For example, the petstore’s listAuditEvents declares a required Authorization header parameter, yet its request struct has no header field. To send credentials in Authorization , declare a security scheme , as that operation also does.

--all-headers

Default (Operation-Referenced Headers Only)

The petstore’s showPetById operation uses an x-api-version header. The document also declares an x-debug-trace header parameter in components/parameters that no operation uses. Start with the default options:

cargo run -- generate types -i petstore.json -o types.rs

Among the other header constants, the output contains one for the header the operation uses:

pub const X_API_VERSION: http::HeaderName = http::HeaderName::from_static(
    "x-api-version",
);

x-debug-trace isn’t emitted because no operation uses it.

With --all-headers

Now include the unused component header:

cargo run -- generate types -i petstore.json -o types.rs --all-headers

The output gains a constant for it:

pub const X_DEBUG_TRACE: http::HeaderName = http::HeaderName::from_static(
    "x-debug-trace",
);

Builder Generation

Use --enable-builders when you want to construct generated values through setter methods. The generator adds bon::Builder derives to schema structs and #[builder] constructors to request structs.

--enable-builders

Builders are disabled by default. The Builder Pattern chapter walks through using them and explains how request builders run validation.

Default (Builders Disabled)

Without the flag, generated structs have no bon attributes. This excerpt from the petstore client shows a schema type and a request type:

/// A pet in the store's care.
#[derive(Debug, Clone, PartialEq, Deserialize, oas3_gen_support::Default)]
pub struct Pet {
    /// Server-assigned identifier
    pub id: uuid::Uuid,
    pub name: String,
    /* ... */
}

/// Get a pet
#[derive(Debug, Clone, validator::Validate, oas3_gen_support::Default)]
pub struct ShowPetByIdRequest {
    pub path: ShowPetByIdRequestPath,
    #[validate(nested)]
    pub header: ShowPetByIdRequestHeader,
}

With --enable-builders

With the flag, the schema gets a derive and the request gets a constructor annotated for bon :

/// A pet in the store's care.
#[derive(Debug, Clone, PartialEq, Deserialize, oas3_gen_support::Default, bon::Builder)]
pub struct Pet {
    /// Server-assigned identifier
    pub id: uuid::Uuid,
    pub name: String,
    /* ... */
}

/// Get a pet
#[derive(Debug, Clone, validator::Validate, oas3_gen_support::Default)]
pub struct ShowPetByIdRequest {
    pub path: ShowPetByIdRequestPath,
    #[validate(nested)]
    pub header: ShowPetByIdRequestHeader,
}

#[bon::bon]
impl ShowPetByIdRequest {
    /// Create a new request with the given parameters.
    #[builder]
    pub fn new(
        pet_id: uuid::Uuid,
        if_none_match: Option<String>,
        x_api_version: String,
    ) -> anyhow::Result<Self> {
        /* ... */
    }
}

The constructor accepts each parameter separately, so the builder has a setter for pet_id , if_none_match , and x_api_version . It assembles the nested path and header structs and validates the request before returning it.

Documentation Formatting

Descriptions and summaries in the OpenAPI document become documentation comments in the generated code. Use --doc-format to format longer comments with the external mdformat tool. The generator sends text longer than 100 bytes to mdformat with a line-wrap setting of 100 characters.

--doc-format

Install mdformat and make sure it’s available on your PATH before using the flag:

pip install mdformat

Default (Formatting Disabled)

Without the flag, the generator preserves documentation text while converting escaped newlines to line breaks. A long description can therefore remain on one line, as the petstore’s CollarTelemetry description does:

/// A batch of smart-collar readings, written with JSON Schema 2020-12 keywords. Keywords without a Rust equivalent are accepted and ignored.
pub struct CollarTelemetry {
    /* ... */
}

With --doc-format

cargo run -- generate types -i petstore.json -o types.rs --doc-format

The same description is wrapped into multiple lines:

/// A batch of smart-collar readings, written with JSON Schema 2020-12 keywords. Keywords without a Rust
/// equivalent are accepted and ignored.
pub struct CollarTelemetry {
    /* ... */
}

Known Limitations

The following constraints and serialization options aren’t fully represented in the generated code. The petstore examples show where each limitation applies:

  • With --no-ordered-collections, an array with uniqueItems: true, such as the tags of Pet, is a Vec and accepts repeated items. See Ordering and Collections.
  • An optional field that also allows null is an Option<T>, so JSON null and a missing property both decode as None, and None is left out when serializing. A client therefore can’t send null to clear a field with a JSON merge patch, such as the nickname in the PetPatch body of updatePet. Likewise, a field without a type, such as anyValue in CollarTelemetry, decodes null as None.
  • The generator ignores allowReserved on query parameters. The client percent-encodes reserved characters, such as the / in a q value for searchPets.
  • A query parameter’s value must be a scalar or an array of scalars. A JSON object or an array of arrays in an untyped parameter, such as filter on searchPets, makes the client method return an error before it sends the request.
  • An anyOf whose branches each require a different property isn’t enforced. The PetCarrier schema requires either wheels or straps, but both become optional fields, so a carrier with neither decodes successfully.
  • A multipart part name can’t contain ", \, a carriage return, a line feed, or another control character. The generator warns about quotes and line breaks, as described in Generation Warnings.
  • A multipleOf that the generator can’t check exactly only appears in the field’s documentation. See Multiples.

Flag Summary

This table collects the options used across the generation chapters. Defaults apply when you omit the corresponding option. Run oas3-gen generate --help to see the command-line help, including input, output, and terminal options.

FlagDefaultDescription
modetypesGeneration mode: types, client, client-mod, server-mod
-w, --workspacefalseEmit a workspace-compatible Cargo.toml with sources in src/; requires client-mod or server-mod
--module-version0.0.0Version written to the generated Cargo.toml [package] table; requires --workspace
-C, --visibilitypublicItem visibility: public, crate, file
--enum-modemergeEnum duplicate handling: merge, preserve, relaxed
--enum-layoutspecVariant ordering: spec, sorted
--no-helpersfalseDisable enum constructor helpers
--odata-supportfalseMake @odata.* fields optional
-c, --customize(none)Custom serde_with adapter; repeatable
--fn-name(none)Custom function name per operation (ID=NAME); repeatable
--api-name(none)Name for the generated client struct or server trait
--all-headersfalseEmit header constants for all component-level headers
--enable-buildersfalseEnable bon builder derives and methods
--no-ordered-collectionsfalseEmit HashMap/Vec instead of indexmap collection types
--doc-formatfalseFormat documentation comments with mdformat
--only(none)Include only specified operations
--exclude(none)Exclude specified operations
--all-schemasfalseGenerate all schemas regardless of usage