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

OpenAPI support

openapi-trait reads OpenAPI 3.0 and 3.1 documents in YAML or JSON. Swagger (OpenAPI 2.0) documents are rejected with a compile error; convert them to OpenAPI 3 first.

Warnings and errors

When the macro can’t represent part of a spec, it tells you at compile time, pointing at the spec path in the attribute:

  • Errors stop the build. They cover anything that would make the generated code wrong, such as a missing operationId or an operation that requires an unsupported security scheme.
  • Warnings cover things that are skipped while the rest still works, such as a cookie parameter or a 2XX response range. They are ordinary compiler warnings, so cargo, rust-analyzer and cached builds all show them.

Stable Rust has no way for a macro to emit its own warnings, so each one is reported through the deprecated lint and reads like this:

warning: use of deprecated constant `_::openapi_trait_warning`: openapi-trait: operation `listPets`: cookie parameter `session` is not supported and was skipped
 --> src/lib.rs:1:23
  |
1 | #[openapi_trait::axum("openapi.yaml")]
  |                       ^^^^^^^^^^^^^^

To silence them, put #[allow(deprecated)] (or #[expect(deprecated)]) on the module or crate that contains the annotated module. An attribute on the annotated module itself doesn’t reach them.

Schemas

FeatureStatus
components/schemas → structs✅
Required vs. optional properties✅ optional properties become Option<T> and are left out of the JSON when None
nullable: true (3.0), type: [T, "null"] (3.1)✅ Option<T>, also for array items and map values; ignored for parameters and headers
oneOf / anyOf with a {type: "null"} branch✅ Option<T> of the other branch; with several other branches, an Option of the enum
String enums✅ Rust enums, with #[serde(rename)] per variant
const (3.1) on a string✅ a one-variant enum
additionalProperties✅ HashMap<String, T>
String format✅ date-time → chrono::DateTime<Utc>, date → chrono::NaiveDate, uuid → uuid::Uuid, binary → Vec<u8>; other formats, including email, stay String (chrono and uuid are re-exported)
oneOf✅ a tagged enum if a discriminator is present, otherwise #[serde(untagged)]
allOf✅ one merged struct: $ref branches via #[serde(flatten)], inline objects inlined
anyOf✅ #[serde(untagged)] enum
Own properties next to oneOf / anyOf / allOf, or several of these keywords in one schema✅ one struct: the own properties as fields, allOf branches merged as above, and a #[serde(flatten)] field one_of / any_of holding a {Name}OneOf / {Name}AnyOf enum. A oneOf discriminator’s tag property stays with the enum. A property that also appears in a branch is only filled in the struct field
oneOf / anyOf next to additionalPropertiesfalls back to serde_json::Value
Inline objects, enums and compositions in properties✅ lifted to {ParentStruct}{Property}
Inline request and response body schemas✅ Body and Status{code}Body in the operation’s module
Recursive schemas✅ the field that closes a cycle becomes Box<T> (Option<Box<T>> when optional); references inside arrays and maps aren’t boxed
not, unconstrained schemas, several types (type: [string, integer])fall back to serde_json::Value
Constraints (minLength, minimum, pattern, …)✅ with the validation feature; exclusiveMinimum / exclusiveMaximum in both their 3.0 (boolean) and 3.1 (numeric) form

Operations

FeatureStatus
Path parameters, including enums✅
Query parameters, including enums and array style/explode✅
Header parameters, including enums✅ typed like query parameters; arrays use style: simple (a,b,c). The server returns 400 for a value that doesn’t parse
JSON request bodies✅ an optional body (required: false) is None when no JSON Content-Type is sent
JSON media types (application/*+json, charset parameters)✅ typed like application/json; the declared media type is sent as the Content-Type
Other request body media types (text/plain, forms, multipart)not supported: the body is skipped with a warning
Binary request bodies✅ streamed: axum::body::Body on the server, reqwest::Body on the client
One response enum per operation✅
default responses✅ a Default { status, body, … } variant, typed like a numbered response (body and headers); status is any status the operation doesn’t declare
Response headers (Content-Disposition, ETag, Cache-Control, …)✅ a payload struct holding the body and headers
Binary downloads✅ streamed: axum::body::Body on the server, openapi_trait::ByteStream on the client

Security

SchemeStatus
apiKey in header, query or cookie✅
http bearer✅
http basic✅
oauth2, openIdConnect✅ handled like http bearer; scopes are not checked
http with another scheme (digest, …), mutualTLS, apiKey with another in, $ref schemes❌ an operation that requires one fails to compile

See Security for details.

OpenAPI 3.1

A 3.0 document is converted to 3.1 when it’s read, so both versions produce the same code. These 3.1 features are ignored:

  • prefixItems, if / then / else, dependentSchemas, patternProperties, unevaluatedProperties and $dynamicRef
  • keywords next to a $ref
  • contentEncoding and contentMediaType; use format: binary or a binary media type for binary bodies
  • webhooks and components/pathItems

Not supported

If something you need isn’t in these tables, please open an issue and attach a minimal spec.