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
operationIdor 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
2XXresponse 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
| Feature | Status |
|---|---|
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 additionalProperties | falls 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
| Feature | Status |
|---|---|
| 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
| Scheme | Status |
|---|---|
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,unevaluatedPropertiesand$dynamicRef- keywords next to a
$ref contentEncodingandcontentMediaType; useformat: binaryor a binary media type for binary bodieswebhooksandcomponents/pathItems
Not supported
If something you need isn’t in these tables, please open an issue and attach a minimal spec.