Skip to main content

mx20022_codegen/ir/
types.rs

1//! Intermediate representation types.
2//!
3//! The IR sits between the raw XSD AST ([`crate::xsd::Schema`]) and the final
4//! emitted Rust token stream.  It is closer to Rust than XSD: XML names have
5//! been mapped to Rust identifiers, cardinality has been resolved into
6//! [`Cardinality`] variants, and built-in XSD types have been mapped to
7//! [`RustType`].
8//!
9//! # Entry point
10//!
11//! See [`TypeGraph`] for the top-level structure produced by
12//! [`crate::ir::lower::lower`].
13
14use indexmap::IndexMap;
15
16/// The complete intermediate representation of a single XSD schema.
17///
18/// # Example
19///
20/// ```no_run
21/// use mx20022_codegen::{xsd, ir};
22///
23/// let schema = xsd::parse_str(r#"<xs:schema
24///     xmlns:xs="http://www.w3.org/2001/XMLSchema"
25///     targetNamespace="urn:example">
26///   <xs:element name="Root" type="RootType"/>
27///   <xs:complexType name="RootType">
28///     <xs:sequence>
29///       <xs:element name="Id" type="xs:string"/>
30///     </xs:sequence>
31///   </xs:complexType>
32/// </xs:schema>"#).unwrap();
33///
34/// let graph = ir::lower::lower(&schema).unwrap();
35/// assert_eq!(graph.root_elements.len(), 1);
36/// ```
37#[derive(Debug, Clone, PartialEq)]
38pub struct TypeGraph {
39    /// The `targetNamespace` from the XSD schema element.
40    pub namespace: String,
41    /// Top-level document element declarations (e.g. `<xs:element name="AppHdr" …/>`).
42    pub root_elements: Vec<RootElement>,
43    /// All type definitions keyed by their XSD/Rust name, in insertion order.
44    pub types: IndexMap<String, TypeDef>,
45}
46
47/// A top-level element declaration referencing a named type.
48#[derive(Debug, Clone, PartialEq)]
49pub struct RootElement {
50    /// The XML element name (e.g. `"AppHdr"`).
51    pub xml_name: String,
52    /// The type name the element uses (e.g. `"BusinessApplicationHeaderV04"`).
53    pub type_name: String,
54}
55
56/// A single type definition in the IR.
57#[derive(Debug, Clone, PartialEq)]
58pub enum TypeDef {
59    /// A struct with named fields — from `xs:complexType` with `xs:sequence`.
60    Struct(StructDef),
61    /// An enum with typed variants — from `xs:complexType` with `xs:choice`.
62    Enum(EnumDef),
63    /// A newtype wrapping a built-in Rust type — from `xs:simpleType` with
64    /// non-enumeration facets (pattern, length, etc.).
65    Newtype(NewtypeDef),
66    /// A string-valued code enum — from `xs:simpleType` whose restriction
67    /// contains only `xs:enumeration` facets.
68    CodeEnum(CodeEnumDef),
69    /// A value with XML attributes — from `xs:complexType` with
70    /// `xs:simpleContent` / `xs:extension`.
71    ValueWithAttr(ValueWithAttrDef),
72    /// An opaque wrapper for `xs:any` content.
73    Opaque(OpaqueDef),
74}
75
76impl TypeDef {
77    /// Return the name of this type definition.
78    pub fn name(&self) -> &str {
79        match self {
80            TypeDef::Struct(d) => &d.name,
81            TypeDef::Enum(d) => &d.name,
82            TypeDef::Newtype(d) => &d.name,
83            TypeDef::CodeEnum(d) => &d.name,
84            TypeDef::ValueWithAttr(d) => &d.name,
85            TypeDef::Opaque(d) => &d.name,
86        }
87    }
88}
89
90// ── Struct ────────────────────────────────────────────────────────────────────
91
92/// A struct generated from an `xs:complexType` with an `xs:sequence`.
93#[derive(Debug, Clone, PartialEq)]
94pub struct StructDef {
95    /// The Rust struct name (identical to the XSD type name; already `PascalCase`).
96    pub name: String,
97    /// Ordered list of struct fields.
98    pub fields: Vec<FieldDef>,
99}
100
101/// A single field inside a [`StructDef`].
102#[derive(Debug, Clone, PartialEq)]
103pub struct FieldDef {
104    /// Original XML element name (e.g. `"BizMsgIdr"`).
105    pub xml_name: String,
106    /// `snake_case` Rust field name (e.g. `"biz_msg_idr"`).
107    pub rust_name: String,
108    /// Resolved type reference.
109    pub type_ref: TypeRef,
110    /// Field cardinality, derived from `minOccurs`/`maxOccurs`.
111    pub cardinality: Cardinality,
112}
113
114/// How many times a field may appear.
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub enum Cardinality {
117    /// `minOccurs=1`, `maxOccurs=1` — plain field.
118    Required,
119    /// `minOccurs=0`, `maxOccurs=1` — `Option<T>`.
120    Optional,
121    /// `maxOccurs="unbounded"` — `Vec<T>`.
122    Vec,
123    /// `maxOccurs=N` where `N > 1` — `Vec<T>` with a documented upper bound.
124    BoundedVec(u32),
125}
126
127// ── Enum ──────────────────────────────────────────────────────────────────────
128
129/// An enum generated from an `xs:complexType` with an `xs:choice`.
130#[derive(Debug, Clone, PartialEq)]
131pub struct EnumDef {
132    /// The Rust enum name.
133    pub name: String,
134    /// Ordered list of enum variants.
135    pub variants: Vec<VariantDef>,
136}
137
138/// A single variant inside an [`EnumDef`].
139#[derive(Debug, Clone, PartialEq)]
140pub struct VariantDef {
141    /// Original XML element name used as the variant label (e.g. `"OrgId"`).
142    pub xml_name: String,
143    /// Rust variant name — kept as `PascalCase` since that is already idiomatic
144    /// for enum variants (e.g. `"OrgId"`).
145    pub rust_name: String,
146    /// The inner type carried by this variant.
147    pub type_ref: TypeRef,
148}
149
150// ── Newtype ───────────────────────────────────────────────────────────────────
151
152/// A newtype wrapping a primitive type with optional validation constraints.
153///
154/// Generated from `xs:simpleType` restrictions that do **not** consist solely
155/// of `xs:enumeration` facets.
156#[derive(Debug, Clone, PartialEq)]
157pub struct NewtypeDef {
158    /// The Rust struct name.
159    pub name: String,
160    /// The wrapped primitive Rust type.
161    pub inner: RustType,
162    /// Zero or more constraints derived from XSD facets.
163    pub constraints: Vec<Constraint>,
164}
165
166// ── CodeEnum ──────────────────────────────────────────────────────────────────
167
168/// A string-code enum generated from `xs:simpleType` with `xs:enumeration`
169/// facets.
170///
171/// # Example
172///
173/// XSD `AddressType2Code` with values `"ADDR"`, `"PBOX"`, … becomes:
174/// ```text
175/// pub enum AddressType2Code { Addr, Pbox, … }
176/// ```
177#[derive(Debug, Clone, PartialEq)]
178pub struct CodeEnumDef {
179    /// The Rust enum name.
180    pub name: String,
181    /// The enumerated code values.
182    pub codes: Vec<CodeValue>,
183}
184
185/// A single code value within a [`CodeEnumDef`].
186#[derive(Debug, Clone, PartialEq)]
187pub struct CodeValue {
188    /// The raw XML string value (e.g. `"ADDR"`).
189    pub xml_value: String,
190    /// The Rust variant name in `PascalCase` (e.g. `"Addr"`).
191    pub rust_name: String,
192}
193
194// ── ValueWithAttr ─────────────────────────────────────────────────────────────
195
196/// A value type that also carries XML attributes.
197///
198/// Generated from `xs:complexType` with `xs:simpleContent` / `xs:extension`.
199#[derive(Debug, Clone, PartialEq)]
200pub struct ValueWithAttrDef {
201    /// The Rust struct name.
202    pub name: String,
203    /// The base value type (the `xs:extension base` resolved to an IR ref).
204    pub value_type: TypeRef,
205    /// XML attributes on this element.
206    pub attributes: Vec<AttrDef>,
207}
208
209/// A single XML attribute inside a [`ValueWithAttrDef`].
210#[derive(Debug, Clone, PartialEq)]
211pub struct AttrDef {
212    /// Original XML attribute name (e.g. `"Ccy"`).
213    pub xml_name: String,
214    /// `snake_case` Rust field name (e.g. `"ccy"`).
215    pub rust_name: String,
216    /// Resolved type reference.
217    pub type_ref: TypeRef,
218    /// Whether `use="required"` was specified on the attribute.
219    pub required: bool,
220}
221
222// ── Opaque ────────────────────────────────────────────────────────────────────
223
224/// An opaque wrapper generated from `xs:complexType` with `xs:any`.
225#[derive(Debug, Clone, PartialEq)]
226pub struct OpaqueDef {
227    /// The Rust struct name.
228    pub name: String,
229    /// The `namespace` attribute of `<xs:any>`, if present.
230    pub namespace: Option<String>,
231}
232
233// ── Shared references and primitives ─────────────────────────────────────────
234
235/// A reference to a type in the IR.
236#[derive(Debug, Clone, PartialEq)]
237pub enum TypeRef {
238    /// Reference to another named type in the [`TypeGraph`].
239    Named(String),
240    /// A built-in primitive Rust type.
241    Builtin(RustType),
242}
243
244/// Primitive Rust types that XSD built-in types map to.
245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
246pub enum RustType {
247    /// `xs:string`, `xs:normalizedString`, `xs:token`, etc. → `String`
248    String,
249    /// `xs:boolean` → `bool`
250    Bool,
251    /// `xs:decimal` → stored as `String` for now; can be refined to
252    /// `rust_decimal::Decimal` later.
253    Decimal,
254    /// `xs:date` → stored as `String`.
255    Date,
256    /// `xs:dateTime` → stored as `String`.
257    DateTime,
258}
259
260/// A constraint derived from an XSD facet applied to a [`NewtypeDef`].
261#[derive(Debug, Clone, PartialEq)]
262pub enum Constraint {
263    /// `xs:minLength`
264    MinLength(u64),
265    /// `xs:maxLength`
266    MaxLength(u64),
267    /// `xs:pattern`
268    Pattern(String),
269    /// `xs:minInclusive`
270    MinInclusive(String),
271    /// `xs:maxInclusive`
272    MaxInclusive(String),
273    /// `xs:totalDigits`
274    TotalDigits(u32),
275    /// `xs:fractionDigits`
276    FractionDigits(u32),
277}