Skip to main content

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}