oapi_codegen/ir.rs
1//! Intermediate representation (IR) of generated Rust types.
2//!
3//! The schema mapping pass ([`crate::lower::schema`]) lowers OpenAPI schemas into this
4//! IR. The emit pass ([`crate::emit`]) turns the IR into a token stream. Keeping
5//! the two separate makes the mapping logic testable without touching token
6//! generation, and keeps emission free of OpenAPI concerns.
7
8use crate::naming::RustIdent;
9
10/// A generated Rust source module: an ordered list of top-level items.
11#[derive(Debug, Default, Clone, PartialEq)]
12pub struct Module {
13 /// Top-level type declarations, in deterministic emission order.
14 pub items: Vec<Item>,
15}
16
17/// A top-level item declaration.
18#[derive(Debug, Clone, PartialEq)]
19#[non_exhaustive]
20pub enum Item {
21 /// A `struct` declaration.
22 Struct(Struct),
23 /// An `enum` declaration (string enum or union).
24 Enum(Enum),
25 /// A `type X = Y;` alias.
26 Alias(Alias),
27}
28
29impl Item {
30 /// The declared name of the item, used for stable ordering.
31 pub fn name(&self) -> &str {
32 let name = match self {
33 Item::Struct(s) => s.name.logical(),
34 Item::Enum(e) => e.name.logical(),
35 Item::Alias(a) => a.name.logical(),
36 };
37 return name;
38 }
39}
40
41/// A generated `struct`.
42#[derive(Debug, Clone, PartialEq)]
43pub struct Struct {
44 /// Type name.
45 pub name: RustIdent,
46 /// Doc comment derived from the schema `description`.
47 pub doc: Option<String>,
48 /// `#[deprecated]` annotation from `deprecated: true` (+ `x-deprecated-reason`).
49 pub deprecated: Option<Deprecation>,
50 /// Named fields, in deterministic order.
51 pub fields: Vec<Field>,
52 /// When set, the struct captures unknown keys into a flattened map of this
53 /// element type (`additionalProperties`).
54 pub additional_properties: Option<RustType>,
55 /// Whether the schema set `additionalProperties: false`, which becomes
56 /// `#[serde(deny_unknown_fields)]`.
57 ///
58 /// This is a field of its own and not the `None` case of
59 /// [`Self::additional_properties`], because an absent `additionalProperties`
60 /// and an explicit `false` are different statements. An absent key permits
61 /// unknown keys and drops them, which is what serde does with no attribute.
62 /// An explicit `false` rejects them. Both give no flattened map, so the map
63 /// alone cannot tell them apart.
64 pub deny_unknown_fields: bool,
65}
66
67/// A single struct field.
68#[derive(Debug, Clone, PartialEq)]
69pub struct Field {
70 /// Rust field identifier.
71 pub name: RustIdent,
72 /// `#[serde(rename = "...")]` value, when the wire name differs.
73 pub rename: Option<String>,
74 /// Doc comment derived from the property `description`.
75 pub doc: Option<String>,
76 /// `#[deprecated]` annotation from `deprecated: true` (+ `x-deprecated-reason`).
77 pub deprecated: Option<Deprecation>,
78 /// Field type (already wrapped in `Option<..>` when optional).
79 pub ty: RustType,
80 /// Whether the property is required. Optional fields get
81 /// `#[serde(skip_serializing_if = "Option::is_none")]` unless [`Self::omit_empty`]
82 /// overrides it.
83 pub required: bool,
84 /// `x-omitempty` override: `Some(true)`/`Some(false)` forces the
85 /// `skip_serializing_if` on/off. `None` keeps the default (skip when optional).
86 pub omit_empty: Option<bool>,
87 /// `x-rust-serde-skip`: drop the field from (de)serialization via `#[serde(skip)]`.
88 pub serde_skip: bool,
89 /// The value that serde uses when the property is absent, from `default`.
90 ///
91 /// An optional property with a default is *not* wrapped in `Option`. Once
92 /// parsed, it always holds a value. Only `nullable` keeps the `Option`,
93 /// because there `null` is a value that the property can carry.
94 ///
95 /// `default: null` never reaches here. The parser reads it as no default at
96 /// all, and serde already leaves a missing `Option` as `None`.
97 pub default: Option<DefaultValue>,
98 /// The validation keywords the property declares, when it declares any.
99 ///
100 /// The generator checks these on the way in, so the check runs only where
101 /// the code deserializes. A response the server writes is not checked.
102 pub constraints: Option<Constraints>,
103}
104
105/// A numeric bound, in the form the document writes it.
106///
107/// A bound holds one number, so it copies. `f64` has no `Eq`, so the list stops
108/// at `PartialEq`.
109#[derive(Debug, Clone, Copy, PartialEq)]
110#[non_exhaustive]
111pub enum Bound {
112 /// A bound on an `integer` schema.
113 Int(i64),
114 /// A bound on a `number` schema.
115 Float(f64),
116}
117
118/// The validation keywords a schema declares.
119///
120/// A field with no keyword carries `None`, so the common schema costs nothing.
121#[derive(Debug, Clone, PartialEq, Default)]
122pub struct Constraints {
123 /// `pattern`: the value must match this regular expression.
124 pub pattern: Option<String>,
125 /// `minLength`, counted in characters, as JSON Schema counts them.
126 pub min_length: Option<usize>,
127 /// `maxLength`, counted in characters.
128 pub max_length: Option<usize>,
129 /// `minimum`.
130 pub minimum: Option<Bound>,
131 /// `maximum`.
132 pub maximum: Option<Bound>,
133 /// `exclusiveMinimum`: makes `minimum` a strict bound.
134 pub exclusive_minimum: bool,
135 /// `exclusiveMaximum`: makes `maximum` a strict bound.
136 pub exclusive_maximum: bool,
137 /// Whether `minimum` moved to absorb an `exclusiveMinimum`.
138 ///
139 /// The bound then holds a number the document does not write, and a message
140 /// about it must name the number the author wrote instead.
141 pub folded_minimum: bool,
142 /// Whether `maximum` moved to absorb an `exclusiveMaximum`.
143 pub folded_maximum: bool,
144 /// `multipleOf`.
145 pub multiple_of: Option<Bound>,
146 /// `minItems`.
147 pub min_items: Option<usize>,
148 /// `maxItems`.
149 pub max_items: Option<usize>,
150 /// `uniqueItems`.
151 pub unique_items: bool,
152 /// `minProperties`.
153 pub min_properties: Option<usize>,
154 /// `maxProperties`.
155 pub max_properties: Option<usize>,
156 /// The type the checks run against, when the field names an alias.
157 ///
158 /// A `$ref` to a constrained scalar gives the field a named type, and the
159 /// name alone does not say which check applies. Lowering resolves the ref
160 /// and records the type behind the name here.
161 pub checked_as: Option<RustType>,
162}
163
164impl Constraints {
165 /// Whether the schema declares no keyword at all.
166 pub fn is_empty(&self) -> bool {
167 return *self == Self::default();
168 }
169}
170
171/// The value that serde uses when a property is absent.
172///
173/// Lowered from the schema `default` and already checked against the field type.
174/// The JSON value alone is not enough to write Rust. `1` is `1` for an integer
175/// field and `1.0` for a number field. `"active"` is a string for a `String`
176/// field and a variant path for an enum.
177#[derive(Debug, Clone, PartialEq)]
178#[non_exhaustive]
179pub enum DefaultValue {
180 /// A string literal.
181 Str(String),
182 /// An integer literal.
183 Int(i64),
184 /// An unsigned integer literal.
185 ///
186 /// An unsigned field takes this rather than [`Self::Int`], which keeps a
187 /// negative value out and reaches the whole range of `u64`, above where
188 /// `i64` stops.
189 UInt(u64),
190 /// A floating-point literal.
191 Float(f64),
192 /// A `bool` literal.
193 Bool(bool),
194 /// A unit variant of a generated string enum, named by its identifier.
195 Variant(RustIdent),
196 /// An empty collection, which `Default::default()` gives for both `Vec` and
197 /// `HashMap`.
198 Empty,
199}
200
201/// A generated `enum`.
202#[derive(Debug, Clone, PartialEq)]
203pub struct Enum {
204 /// Type name.
205 pub name: RustIdent,
206 /// Doc comment derived from the schema `description`.
207 pub doc: Option<String>,
208 /// `#[deprecated]` annotation from `deprecated: true` (+ `x-deprecated-reason`).
209 pub deprecated: Option<Deprecation>,
210 /// The flavour of enum to emit.
211 pub kind: EnumKind,
212}
213
214/// The flavour of a generated enum.
215#[derive(Debug, Clone, PartialEq)]
216#[non_exhaustive]
217pub enum EnumKind {
218 /// A C-like string enum: unit variants mapped to wire strings.
219 Strings(Vec<StringVariant>),
220 /// A C-like integer enum: unit variants with explicit discriminants.
221 ///
222 /// The wire form is a bare number, so the emitted type carries
223 /// `#[serde(try_from, into)]` and a `#[repr]` of [`Self::Integers::repr`].
224 Integers {
225 /// The integer type the discriminants take: `i32`, `i64`, `u32`, or `u64`.
226 repr: RustType,
227 /// The permitted values, in document order.
228 variants: Vec<IntegerVariant>,
229 },
230 /// A `#[serde(untagged)]` union over the given newtype variants.
231 ///
232 /// Untagged (rather than internally tagged) is used even when the OpenAPI
233 /// schema has a discriminator: OpenAPI variant schemas typically carry the
234 /// discriminator property themselves, which is incompatible with serde's
235 /// internally-tagged representation.
236 Union(Vec<UnionVariant>),
237}
238
239/// A unit variant of a string enum.
240#[derive(Debug, Clone, PartialEq)]
241pub struct StringVariant {
242 /// Rust variant identifier.
243 pub name: RustIdent,
244 /// `#[serde(rename = "...")]` value, when the wire value differs.
245 pub rename: Option<String>,
246 /// Doc comment, if any.
247 pub doc: Option<String>,
248}
249
250/// A unit variant of an integer enum.
251#[derive(Debug, Clone, PartialEq)]
252pub struct IntegerVariant {
253 /// Rust variant identifier.
254 pub name: RustIdent,
255 /// The discriminant, and the value on the wire.
256 pub value: i64,
257 /// Doc comment, if any.
258 pub doc: Option<String>,
259}
260
261/// A newtype variant of a union enum.
262#[derive(Debug, Clone, PartialEq)]
263pub struct UnionVariant {
264 /// Rust variant identifier.
265 pub name: RustIdent,
266 /// The wrapped type.
267 pub ty: RustType,
268}
269
270/// A generated `type X = Y;` alias.
271#[derive(Debug, Clone, PartialEq)]
272pub struct Alias {
273 /// Alias name.
274 pub name: RustIdent,
275 /// Doc comment, if any.
276 pub doc: Option<String>,
277 /// `#[deprecated]` annotation from `deprecated: true` (+ `x-deprecated-reason`).
278 pub deprecated: Option<Deprecation>,
279 /// Aliased type.
280 pub ty: RustType,
281}
282
283/// A `#[deprecated]` annotation derived from `deprecated: true`, optionally
284/// carrying the `x-deprecated-reason` text as the `note`.
285#[derive(Debug, Clone, PartialEq)]
286pub struct Deprecation {
287 /// The `x-deprecated-reason` text, emitted as `#[deprecated(note = "...")]`.
288 pub note: Option<String>,
289}
290
291/// Which of the three non-serde traits every generated model derives a foreign
292/// type satisfies.
293///
294/// The generator derives `Debug`, `Clone`, and `PartialEq` on every model. A
295/// model that holds a type the generator did not write cannot derive a trait that
296/// type lacks. The generator also cannot inspect the type, because the
297/// specification names it as text and `rustc` resolves it much later. So the
298/// specification author declares it, with `x-rust-derive`.
299///
300/// [`Self::default`] claims all three. That keeps the output of every
301/// specification written before this feature identical, and it is right far more
302/// often than not. `uuid::Uuid`, the `chrono` types, and a typical hand-written
303/// domain type all derive the three. A target that does not is the case worth one
304/// line of specification.
305#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306pub struct ForeignDerives {
307 /// The target implements [`std::fmt::Debug`].
308 pub debug: bool,
309 /// The target implements [`Clone`].
310 pub clone: bool,
311 /// The target implements [`PartialEq`].
312 pub partial_eq: bool,
313}
314
315impl Default for ForeignDerives {
316 fn default() -> Self {
317 return Self {
318 debug: true,
319 clone: true,
320 partial_eq: true,
321 };
322 }
323}
324
325impl ForeignDerives {
326 /// Whether this target satisfies all three traits, and therefore constrains
327 /// nothing. The common case, and the one that needs no record anywhere.
328 pub(crate) fn is_unconstrained(self) -> bool {
329 return self.debug && self.clone && self.partial_eq;
330 }
331
332 /// Narrow to the traits both `self` and `other` satisfy.
333 ///
334 /// A model holding two foreign types can derive only what both allow, so the
335 /// walk over a model's referenced types folds with this.
336 pub(crate) fn intersect(self, other: Self) -> Self {
337 return Self {
338 debug: self.debug && other.debug,
339 clone: self.clone && other.clone,
340 partial_eq: self.partial_eq && other.partial_eq,
341 };
342 }
343}
344
345/// A Rust type expression usable in field/alias position.
346#[derive(Debug, Clone, PartialEq, Eq)]
347#[non_exhaustive]
348pub enum RustType {
349 /// `bool`.
350 Bool,
351 /// `i32`.
352 I32,
353 /// `i64`.
354 I64,
355 /// `u32`.
356 U32,
357 /// `u64`.
358 U64,
359 /// `f64`.
360 F64,
361 /// `String`.
362 String,
363 /// `serde_json::Value` (free-form / empty schema).
364 Value,
365 /// `chrono::NaiveDate` (`format: date`).
366 Date,
367 /// `chrono::DateTime<chrono::Utc>` (`format: date-time`).
368 DateTime,
369 /// `uuid::Uuid` (`format: uuid`).
370 Uuid,
371 /// `Vec<u8>` (`format: byte`/`binary`).
372 Bytes,
373 /// `Vec<T>`.
374 Vec(Box<RustType>),
375 /// `std::collections::HashMap<String, T>`.
376 Map(Box<RustType>),
377 /// `Option<T>`.
378 Option(Box<RustType>),
379 /// `Box<T>`, added by the recursion pass to give a cyclic type a size.
380 ///
381 /// Nothing in a schema asks for this. `lower::recurse` inserts it where a
382 /// type would otherwise hold itself, directly or through other types.
383 Boxed(Box<RustType>),
384 /// A reference to a named (generated or external) type.
385 Named(String),
386 /// A reference to a type from an import-mapped module: `module::Name`.
387 ///
388 /// Generated by another run of this generator, so it derives all three
389 /// non-serde traits and carries no [`ForeignDerives`].
390 External { module: String, name: String },
391 /// A verbatim type expression from an `x-rust-type` extension, with what its
392 /// `x-rust-derive` says the target implements.
393 Verbatim {
394 /// The Rust type expression, emitted as written.
395 text: String,
396 /// Which of `Debug`, `Clone`, `PartialEq` the target satisfies.
397 derives: ForeignDerives,
398 },
399}
400
401impl RustType {
402 /// Wrap this type in `Option<..>`.
403 pub fn optional(self) -> RustType {
404 return RustType::Option(Box::new(self));
405 }
406
407 /// Whether this type is an `Option<..>` (for which `skip_serializing_if =
408 /// "Option::is_none"` is valid).
409 pub fn is_option(&self) -> bool {
410 return matches!(self, RustType::Option(_));
411 }
412
413 /// The type inside any `Option` or `Box` wrapper.
414 ///
415 /// A `Vec` and a `Map` are not wrappers here. Each carries keywords of its
416 /// own, so a rule reads the collection and not an element.
417 pub fn innermost(&self) -> &RustType {
418 return match self {
419 RustType::Option(inner) | RustType::Boxed(inner) => inner.innermost(),
420 other => other,
421 };
422 }
423
424 /// Whether this is a scalar the generated code compares with `==`.
425 pub fn is_scalar(&self) -> bool {
426 return matches!(
427 self,
428 RustType::Bool
429 | RustType::I32
430 | RustType::I64
431 | RustType::U32
432 | RustType::U64
433 | RustType::F64
434 | RustType::String
435 );
436 }
437
438 /// The type as text, for an error message.
439 ///
440 /// Emission goes through `emit::emit_type`, which builds tokens. This gives
441 /// a reader the same type in a message that the lowering stage can write,
442 /// where no token stream exists yet.
443 pub fn label(&self) -> String {
444 return match self {
445 RustType::Bool => "bool".to_owned(),
446 RustType::I32 => "i32".to_owned(),
447 RustType::I64 => "i64".to_owned(),
448 RustType::U32 => "u32".to_owned(),
449 RustType::U64 => "u64".to_owned(),
450 RustType::F64 => "f64".to_owned(),
451 RustType::String => "String".to_owned(),
452 RustType::Value => "serde_json::Value".to_owned(),
453 RustType::Date => "chrono::NaiveDate".to_owned(),
454 RustType::DateTime => "chrono::DateTime<chrono::Utc>".to_owned(),
455 RustType::Uuid => "uuid::Uuid".to_owned(),
456 RustType::Bytes => "Vec<u8>".to_owned(),
457 RustType::Vec(inner) => format!("Vec<{}>", inner.label()),
458 RustType::Map(inner) => format!("std::collections::HashMap<String, {}>", inner.label()),
459 RustType::Option(inner) => format!("Option<{}>", inner.label()),
460 RustType::Boxed(inner) => format!("Box<{}>", inner.label()),
461 RustType::Named(name) => name.clone(),
462 RustType::External { module, name } => format!("{module}::{name}"),
463 RustType::Verbatim { text, .. } => text.clone(),
464 };
465 }
466
467 /// An `x-rust-type` target whose `x-rust-derive` is absent, so it claims all
468 /// three non-serde traits. The common case, and the shape every test that
469 /// does not test this feature wants.
470 pub fn verbatim(text: impl Into<String>) -> RustType {
471 return RustType::Verbatim {
472 text: text.into(),
473 derives: ForeignDerives::default(),
474 };
475 }
476}
477
478/// A request or response body: its Rust type plus the wire content type that
479/// selects the axum extractor / response wrapper.
480#[derive(Debug, Clone, PartialEq)]
481pub struct Body {
482 /// The Rust type of the decoded body.
483 pub ty: RustType,
484 /// The content type that selects the extractor / response wrapper.
485 pub kind: BodyKind,
486}
487
488/// The supported request/response content type for a [`Body`].
489#[derive(Debug, Clone, Copy, PartialEq, Eq)]
490#[non_exhaustive]
491pub enum BodyKind {
492 /// `application/json` (and `+json` / charset variants) → `axum::Json`.
493 Json,
494 /// `text/plain` → `String`.
495 Text,
496 /// `application/x-www-form-urlencoded` → `axum::Form`.
497 Form,
498 /// `multipart/form-data` (request bodies only) → a hand-written
499 /// `FromRequest` extractor driving `axum::extract::Multipart`. Carried
500 /// alongside an [`Operation`]'s [`Multipart`], which holds the per-field
501 /// parsing detail the extractor needs.
502 Multipart,
503}
504
505/// A `multipart/form-data` request body lowered into a per-operation extractor.
506///
507/// axum has no *typed* multipart extractor, so the generator emits a dedicated
508/// struct (an operation artifact, like the query/header/cookie structs) plus a
509/// hand-written `FromRequest` implementation that drives
510/// `axum::extract::Multipart`, reads each declared field, and returns a
511/// `400 Bad Request` on a missing required field or an unparseable value.
512#[derive(Debug, Clone, PartialEq)]
513pub struct Multipart {
514 /// Struct name (`<Op>Multipart`), doubling as the generated `FromRequest`
515 /// extractor type and the `Api` method's `body` argument type.
516 pub name: RustIdent,
517 /// Fields parsed from the multipart stream, in declaration order.
518 pub fields: Vec<MultipartField>,
519}
520
521/// A single field of a [`Multipart`] body.
522#[derive(Debug, Clone, PartialEq)]
523pub struct MultipartField {
524 /// Wire field name (the part's `Content-Disposition` `name`).
525 pub wire_name: String,
526 /// Target struct field identifier on the generated `<Op>Multipart` struct.
527 pub rust_name: RustIdent,
528 /// The decoded field type: `Vec<u8>` for a binary (file) part, else a
529 /// scalar. This is the bare inner type even when the field is optional.
530 /// [`MultipartField::optional`] records whether the struct wraps it in
531 /// `Option<..>`.
532 pub ty: RustType,
533 /// Whether the generated struct wraps this field in `Option<..>` (true when
534 /// the property is not `required`, or is `nullable`). An absent
535 /// non-optional field is a `400`. An absent optional field is `None`.
536 pub optional: bool,
537 /// Whether the part is a binary/file field read as raw bytes (`Vec<u8>`).
538 pub is_file: bool,
539}
540
541/// An operation's request payload, when it declares one.
542///
543/// The three cases are mutually exclusive by construction, replacing what will
544/// otherwise be several mutually-exclusive `Option` fields on [`Operation`].
545#[derive(Debug, Clone, PartialEq)]
546#[non_exhaustive]
547pub enum RequestPayload {
548 /// A single supported content type (JSON, `text/plain`, or form), extracted
549 /// directly by the matching axum extractor.
550 Single(Body),
551 /// A `multipart/form-data` body, decoded by a generated [`Multipart`]
552 /// extractor.
553 Multipart(Multipart),
554 /// Several supported content types, dispatched at request time on the
555 /// incoming `Content-Type` header by a generated [`NegotiatedBody`]
556 /// `FromRequest` enum. An unrecognised or missing content type yields a
557 /// `415 Unsupported Media Type`.
558 Negotiated(NegotiatedBody),
559}
560
561/// A body offering several content-type representations, lowered into a
562/// generated enum with one variant per representation.
563///
564/// For a request the enum is a hand-written `FromRequest` that dispatches on
565/// `Content-Type`. for a response it is a plain enum the handler selects a
566/// representation from, which the generated `IntoResponse` renders with the
567/// matching `Content-Type`.
568#[derive(Debug, Clone, PartialEq)]
569pub struct NegotiatedBody {
570 /// Enum name — `<Op>RequestBody` for a request, `<Response><Variant>Body`
571 /// for a response — doubling as the argument/field type on the generated
572 /// interface.
573 pub name: RustIdent,
574 /// One variant per supported content type, in priority order (JSON > form >
575 /// text).
576 pub variants: Vec<BodyVariant>,
577}
578
579/// One content-type representation within a [`NegotiatedBody`].
580#[derive(Debug, Clone, PartialEq)]
581pub struct BodyVariant {
582 /// Variant identifier, named after the content kind (`Json`, `Form`,
583 /// `Text`).
584 pub variant: RustIdent,
585 /// The decoded body type and content kind for this representation.
586 pub body: Body,
587}
588
589/// The body of a response variant, when it declares supported content.
590#[derive(Debug, Clone, PartialEq)]
591#[non_exhaustive]
592pub enum ResponseBody {
593 /// A single supported content type, rendered by the matching axum response
594 /// wrapper.
595 Single(Body),
596 /// Several supported content types the handler chooses among. The generated
597 /// `IntoResponse` renders whichever representation the handler selected.
598 Negotiated(NegotiatedBody),
599}
600
601/// A generated axum server interface: the `Api` trait plus the operations that
602/// back its `Router`.
603#[derive(Debug, Default, Clone, PartialEq)]
604pub struct Service {
605 /// Operations in deterministic (document) order.
606 pub operations: Vec<Operation>,
607 /// Security schemes referenced by at least one operation, in the order they
608 /// are declared in `components.securitySchemes`. The client emitter turns
609 /// each into a credential field and a `with_<scheme>` builder setter. The
610 /// server emitter ignores them (server-side auth is not generated yet).
611 pub security_schemes: Vec<SecurityScheme>,
612}
613
614/// A security scheme the client can apply to outgoing requests.
615///
616/// Derived from a `components.securitySchemes` entry. only the schemes the
617/// client generator can carry as a stored credential are modelled here, plus an
618/// [`SecuritySchemeKind::Unsupported`] catch-all so an operation that *requires*
619/// an unmodelled scheme (for example OAuth2) can be rejected with a clear message.
620#[derive(Debug, Clone, PartialEq)]
621pub struct SecurityScheme {
622 /// The scheme's key in `components.securitySchemes`, matched against each
623 /// [`Operation::security`] entry.
624 pub key: String,
625 /// `snake_case` base name for the generated credential field and the
626 /// `with_<field>` builder setter.
627 pub field: RustIdent,
628 /// How the credential is carried on the request.
629 pub kind: SecuritySchemeKind,
630 /// Doc comment derived from the scheme's `description`.
631 pub doc: Option<String>,
632}
633
634/// How a [`SecurityScheme`]'s credential is applied to a request.
635#[derive(Debug, Clone, PartialEq)]
636#[non_exhaustive]
637pub enum SecuritySchemeKind {
638 /// `type: http, scheme: bearer` — `Authorization: Bearer <token>`.
639 HttpBearer,
640 /// `type: http, scheme: basic` — `Authorization: Basic <base64>`.
641 HttpBasic,
642 /// `type: apiKey, in: header` — the key is sent as the named header.
643 ApiKeyHeader(String),
644 /// `type: apiKey, in: query` — the key is sent as the named query parameter.
645 ApiKeyQuery(String),
646 /// `type: apiKey, in: cookie` — the key is sent as the named cookie.
647 ApiKeyCookie(String),
648 /// A scheme the client cannot carry as a stored credential (`oauth2`,
649 /// `openIdConnect`). The wrapped string is a human-readable reason used when
650 /// rejecting an operation that requires it.
651 Unsupported(String),
652}
653
654/// A single HTTP operation lowered for code generation.
655#[derive(Debug, Clone, PartialEq)]
656pub struct Operation {
657 /// `Api` trait method name (`snake_case`).
658 pub name: RustIdent,
659 /// Per-operation response enum name (`<Name>Response`).
660 pub response_enum: RustIdent,
661 /// Doc comment derived from the operation `summary`/`description`.
662 pub doc: Option<String>,
663 /// Lowercase HTTP method verb (`get`, `post`, …), which is also the
664 /// `axum::routing` helper name.
665 pub method: String,
666 /// Request path template, reused verbatim as the axum route (axum 0.8 and
667 /// OpenAPI share the `/{name}` path-parameter syntax).
668 pub path: String,
669 /// Typed path parameters, in path order.
670 pub path_params: Vec<Param>,
671 /// Generated query-parameter struct, when the operation declares query
672 /// parameters. Its name doubles as the `axum_extra::extract::Query<..>`
673 /// type and the `Api` method's `query` argument type.
674 pub query: Option<Struct>,
675 /// Generated header-parameter struct, when the operation declares header
676 /// parameters. Its name doubles as the generated `FromRequestParts`
677 /// extractor type and the `Api` method's `headers` argument type.
678 pub headers: Option<Headers>,
679 /// Generated cookie-parameter struct, when the operation declares cookie
680 /// parameters. Its name doubles as the generated `FromRequestParts`
681 /// extractor type and the `Api` method's `cookies` argument type.
682 pub cookies: Option<Cookies>,
683 /// Request payload (a single supported content type, a `multipart/form-data`
684 /// extractor, or a `Content-Type`-dispatched set of content types), when the
685 /// operation declares a request body.
686 pub request: Option<RequestPayload>,
687 /// Response variants, in declaration order.
688 pub responses: Vec<ResponseCase>,
689 /// Keys of the security schemes this operation applies, derived from its
690 /// effective security requirement (its own `security`, else the document's
691 /// global `security`). Each key matches a [`SecurityScheme::key`]. Empty
692 /// means the operation is unauthenticated.
693 pub security: Vec<String>,
694}
695
696/// A typed operation parameter (path parameter in the current slice).
697#[derive(Debug, Clone, PartialEq)]
698pub struct Param {
699 /// Rust argument identifier.
700 pub name: RustIdent,
701 /// Parameter type.
702 pub ty: RustType,
703}
704
705/// A generated per-operation header struct, extracted via a hand-written
706/// `axum::extract::FromRequestParts` implementation rather than serde, since
707/// header values are read and parsed individually from the request parts.
708#[derive(Debug, Clone, PartialEq)]
709pub struct Headers {
710 /// Struct name (`<Op>Headers`), doubling as the extractor type and the
711 /// `Api` method's `headers` argument type.
712 pub name: RustIdent,
713 /// Header fields, in declaration order.
714 pub params: Vec<HeaderParam>,
715}
716
717/// A single header parameter within a [`Headers`] struct.
718#[derive(Debug, Clone, PartialEq)]
719pub struct HeaderParam {
720 /// Rust field identifier (`snake_case`).
721 pub name: RustIdent,
722 /// The exact OpenAPI header name, used for the case-insensitive lookup in
723 /// the generated extractor (for example `X-Request-Id`).
724 pub header_name: String,
725 /// The parsed scalar type. Unlike [`Field`], this is the bare element type
726 /// even when the header is optional. The emitter adds the `Option<..>`
727 /// wrapper for absent headers.
728 pub ty: RustType,
729 /// Whether the header is required. A missing required header is a `400`.
730 pub required: bool,
731 /// Doc comment derived from the parameter `description`.
732 pub doc: Option<String>,
733}
734
735/// A generated per-operation cookie struct, extracted via a hand-written
736/// `axum::extract::FromRequestParts` implementation backed by `axum_extra`'s
737/// `CookieJar`.
738#[derive(Debug, Clone, PartialEq)]
739pub struct Cookies {
740 /// Struct name (`<Op>Cookies`), doubling as the extractor type and the
741 /// `Api` method's `cookies` argument type.
742 pub name: RustIdent,
743 /// Cookie fields, in declaration order.
744 pub params: Vec<CookieParam>,
745}
746
747/// A single cookie parameter within a [`Cookies`] struct.
748#[derive(Debug, Clone, PartialEq)]
749pub struct CookieParam {
750 /// Rust field identifier (`snake_case`).
751 pub name: RustIdent,
752 /// The exact OpenAPI cookie name, used for the `CookieJar` lookup.
753 pub cookie_name: String,
754 /// The parsed scalar type. Like [`HeaderParam`], this is the bare element
755 /// type even when optional. The emitter adds the `Option<..>` wrapper.
756 pub ty: RustType,
757 /// Whether the cookie is required. A missing required cookie is a `400`.
758 pub required: bool,
759 /// Doc comment derived from the parameter `description`.
760 pub doc: Option<String>,
761}
762
763/// One arm of an operation's response enum.
764#[derive(Debug, Clone, PartialEq)]
765pub struct ResponseCase {
766 /// Variant identifier, named after the status reason phrase (fixed codes),
767 /// the response-range class, or `Default`.
768 pub variant: RustIdent,
769 /// How the variant's HTTP status code is determined.
770 pub status: ResponseStatus,
771 /// Response body (a single supported content type, or a set of content types
772 /// the handler chooses among), when the response declares supported content.
773 pub body: Option<ResponseBody>,
774 /// Declared response headers written by the generated `IntoResponse`, in
775 /// declaration order. Empty means no headers (the pre-C5 variant shape).
776 pub headers: Vec<ResponseHeader>,
777 /// Doc comment derived from the response `description`.
778 pub doc: Option<String>,
779}
780
781/// A single declared response header written by the generated `IntoResponse`.
782#[derive(Debug, Clone, PartialEq)]
783pub struct ResponseHeader {
784 /// Rust field identifier (`snake_case`).
785 pub name: RustIdent,
786 /// Exact header name as written to the response (for example `X-Request-Id`).
787 pub header_name: String,
788 /// Scalar type serialized to a header value via `ToString`. Bare element
789 /// type even when optional. The emitter adds the `Option<..>` wrapper.
790 pub ty: RustType,
791 /// Whether the header is always written (`false` → `Option<..>` field).
792 pub required: bool,
793 /// Doc comment derived from the header `description`.
794 pub doc: Option<String>,
795}
796
797/// How a response variant's HTTP status code is produced.
798///
799/// Fixed codes are emitted as a compile-time constant. `default` and range
800/// responses have no single code, so the variant instead carries an
801/// `axum::http::StatusCode` the handler supplies at runtime.
802#[derive(Debug, Clone, PartialEq, Eq)]
803#[non_exhaustive]
804pub enum ResponseStatus {
805 /// A concrete status code (for example `200`), emitted as a `StatusCode` constant.
806 Fixed(u16),
807 /// The `default` catch-all response. The handler supplies the status code.
808 Default,
809 /// A status-code range such as `5XX`, carrying the leading digit (`1..=5`).
810 /// the handler supplies a concrete code within the class.
811 Range(u8),
812}
813
814/// The lowered `servers:` block: constants and builder functions for each
815/// declared server URL, plus the enum types their variables reference.
816///
817/// Emitted when `generate.server-urls` is set. A server whose URL has no
818/// `{placeholder}` becomes a `const`. one with placeholders becomes a builder
819/// function that substitutes each variable and validates the result.
820#[derive(Debug, Clone, PartialEq)]
821pub struct ServerUrls {
822 /// Enum types for the enum-constrained server variables, emitted before the
823 /// builder functions that reference them.
824 pub enums: Vec<ServerUrlEnum>,
825 /// One entry per declared server, in document order.
826 pub servers: Vec<ServerUrl>,
827}
828
829/// A single lowered server URL: either a constant or a builder function.
830#[derive(Debug, Clone, PartialEq)]
831#[non_exhaustive]
832pub enum ServerUrl {
833 /// A server URL with no variables: `pub const <NAME>: &str = "<url>";`.
834 Const(ServerUrlConst),
835 /// A server URL with `{placeholder}`s: a builder function substituting them.
836 Builder(ServerUrlBuilder),
837}
838
839/// A variable-free server URL emitted as a string constant.
840#[derive(Debug, Clone, PartialEq)]
841pub struct ServerUrlConst {
842 /// `SCREAMING_SNAKE_CASE` constant name.
843 pub name: RustIdent,
844 /// Doc comment derived from the server `description`.
845 pub doc: Option<String>,
846 /// The literal server URL.
847 pub url: String,
848}
849
850/// A server URL with `{placeholder}`s emitted as a builder function that
851/// substitutes each variable and returns the resulting URL.
852#[derive(Debug, Clone, PartialEq)]
853pub struct ServerUrlBuilder {
854 /// `snake_case` function name.
855 pub name: RustIdent,
856 /// Doc comment derived from the server `description`.
857 pub doc: Option<String>,
858 /// The URL template, retaining its `{placeholder}` tokens.
859 pub url_template: String,
860 /// Parameters, sorted by placeholder name for a deterministic signature.
861 pub params: Vec<ServerUrlParam>,
862}
863
864/// One parameter of a [`ServerUrlBuilder`], bound to a URL placeholder.
865#[derive(Debug, Clone, PartialEq)]
866pub struct ServerUrlParam {
867 /// `snake_case` parameter identifier.
868 pub ident: RustIdent,
869 /// The placeholder name as it appears in the URL (without braces).
870 pub placeholder: String,
871 /// How the parameter is typed and turned into its substituted string.
872 pub ty: ServerUrlParamType,
873}
874
875/// The type of a [`ServerUrlParam`].
876#[derive(Debug, Clone, PartialEq)]
877#[non_exhaustive]
878pub enum ServerUrlParamType {
879 /// A free-form `&str` parameter (a non-enum or undeclared variable).
880 Str,
881 /// An enum-constrained parameter of the named [`ServerUrlEnum`] type.
882 Enum(RustIdent),
883}
884
885/// An enum type generated for an enum-constrained server variable.
886#[derive(Debug, Clone, PartialEq)]
887pub struct ServerUrlEnum {
888 /// `PascalCase` enum type name (`<Server><Variable>`).
889 pub name: RustIdent,
890 /// Doc comment naming the server variable this enum constrains.
891 pub doc: Option<String>,
892 /// The permitted values, in declaration order.
893 pub variants: Vec<ServerUrlEnumVariant>,
894 /// The variant identifier used for the `Default` impl (the OpenAPI
895 /// `default`), when the variable declares one.
896 pub default: Option<RustIdent>,
897}
898
899/// One variant of a [`ServerUrlEnum`]: a Rust identifier and its wire value.
900#[derive(Debug, Clone, PartialEq)]
901pub struct ServerUrlEnumVariant {
902 /// `PascalCase` variant identifier.
903 pub name: RustIdent,
904 /// The wire value substituted into the URL.
905 pub value: String,
906}