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