Skip to main content

zerodds_xml/
xtypes_def.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! DDS-XML 1.0 §7.3.3 building block "Types" — data model.
4//!
5//! Descriptive data model of the XML view of XTypes 1.3 type definitions.
6//!
7//! `zerodds-xml` deliberately keeps a *standalone* descriptive layer and
8//! makes **no** cross-crate edge to `zerodds-types::DynamicType`. Higher-level
9//! adapters can later translate the structures held here into
10//! their DCPS data models.
11//!
12//! Spec source: OMG DDS-XML 1.0 §7.3.3 (Types Building Block).
13//!
14//! # XML → Rust type mapping
15//!
16//! ```text
17//! <types>                          | Vec<TypeLibrary> (multiple <types> allowed)
18//! <module name=…>                  | TypeLibrary / TypeDef::Module(ModuleEntry)
19//! <struct name=… extensibility=…   |
20//!         baseType=…>              | TypeDef::Struct(StructType)
21//! <member name=… type=… key=…      |
22//!         optional=… id=…          |
23//!         arrayDimensions=…        |
24//!         sequenceMaxLength=…      |
25//!         stringMaxLength=…>       | StructMember
26//! <enum name=…>                    | TypeDef::Enum(EnumType)
27//! <enumerator name=… value=…>      | EnumLiteral
28//! <union name=… discriminator=…>   | TypeDef::Union(UnionType)
29//! <case><caseDiscriminator/>       |
30//!       <member …/></case>         | UnionCase
31//! <typedef name=… type=… …>        | TypeDef::Typedef(TypedefType)
32//! <bitmask name=… bitBound=…>      | TypeDef::Bitmask(BitmaskType)
33//! <bit_value name=… position=…>    | BitValue
34//! <bitset name=…>                  | TypeDef::Bitset(BitsetType)
35//! <bitfield name=… type=… mask=…>  | BitField
36//! ```
37
38use alloc::string::String;
39use alloc::vec::Vec;
40
41/// Container for 1+ type definitions from a `<types>` block.
42///
43/// Spec §7.3.3.4: a DDS-XML document may have multiple `<types>` top-level
44/// elements. Each block is modeled as a `TypeLibrary` with an
45/// optional name (annotation, not spec-mandatory).
46#[derive(Debug, Clone, Default, PartialEq, Eq)]
47pub struct TypeLibrary {
48    /// Optional library name (e.g. `<types name="Lib1">`); the spec allows
49    /// the attribute without making it mandatory.
50    pub name: String,
51    /// Type definitions in document order. Modules are embedded as
52    /// `TypeDef::Module(ModuleEntry)` (nested).
53    pub types: Vec<TypeDef>,
54}
55
56impl TypeLibrary {
57    /// Look up a top-level type by its local name.
58    #[must_use]
59    pub fn find(&self, name: &str) -> Option<&TypeDef> {
60        self.types.iter().find(|t| t.name() == name)
61    }
62}
63
64/// A single type entry (Spec §7.3.3.4 — struct/enum/union/typedef/
65/// bitmask/bitset or a nested module).
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub enum TypeDef {
68    /// `<struct>` — XTypes aggregated type.
69    Struct(StructType),
70    /// `<enum>` — XTypes enumerated type.
71    Enum(EnumType),
72    /// `<union>` — XTypes union type.
73    Union(UnionType),
74    /// `<typedef>` — type alias with optional array/sequence modifiers.
75    Typedef(TypedefType),
76    /// `<bitmask>` — XTypes bitmask type.
77    Bitmask(BitmaskType),
78    /// `<bitset>` — XTypes bitset type.
79    Bitset(BitsetType),
80    /// `<module>` — namespacing container; further types nested inside.
81    Module(ModuleEntry),
82    /// `<include>` — pull in an external XML file (DDS-XML 1.0 §7.3.3.4 +
83    /// XTypes 1.3 §7.3.2). Captured as a marker during parse; a resolver
84    /// can resolve it later for composition.
85    Include(IncludeEntry),
86    /// `<forward_dcl>` — forward decl without members (XTypes 1.3 §7.3.2).
87    /// Allows mutually recursive type refs.
88    ForwardDcl(ForwardDeclEntry),
89    /// `<const>` — constant definition (XTypes 1.3 §7.3.2 / IDL 4.2
90    /// §7.4.1.4.4). Value as a string; the caller converts.
91    Const(ConstEntry),
92}
93
94/// `<include file="..."/>` — XML composition.
95#[derive(Debug, Clone, PartialEq, Eq, Default)]
96pub struct IncludeEntry {
97    /// File path relative to the including XML.
98    pub file: String,
99}
100
101/// `<forward_dcl name="T" kind="STRUCT|UNION"/>`.
102#[derive(Debug, Clone, PartialEq, Eq, Default)]
103pub struct ForwardDeclEntry {
104    /// Type name.
105    pub name: String,
106    /// "STRUCT" or "UNION" — the spec allows only these two.
107    pub kind: String,
108}
109
110/// `<const name="X" type="long" value="42"/>`.
111#[derive(Debug, Clone, PartialEq, Eq, Default)]
112pub struct ConstEntry {
113    /// Constant name.
114    pub name: String,
115    /// Type (primitive string like "long", "string", etc.).
116    pub type_name: String,
117    /// Raw value as a string.
118    pub value: String,
119}
120
121impl TypeDef {
122    /// Local name of the type/module.
123    #[must_use]
124    pub fn name(&self) -> &str {
125        match self {
126            Self::Struct(s) => &s.name,
127            Self::Enum(e) => &e.name,
128            Self::Union(u) => &u.name,
129            Self::Typedef(t) => &t.name,
130            Self::Bitmask(b) => &b.name,
131            Self::Bitset(b) => &b.name,
132            Self::Module(m) => &m.name,
133            Self::Include(i) => &i.file,
134            Self::ForwardDcl(f) => &f.name,
135            Self::Const(c) => &c.name,
136        }
137    }
138}
139
140/// Nested module (`<module name="…">` — Spec §7.3.3.4.1).
141#[derive(Debug, Clone, Default, PartialEq, Eq)]
142pub struct ModuleEntry {
143    /// Module name.
144    pub name: String,
145    /// Nested type definitions.
146    pub types: Vec<TypeDef>,
147}
148
149/// Extensibility-Annotation (`@final`, `@appendable`, `@mutable`) — Spec
150/// §7.2.3.5 + §7.3.3.4.4.1.
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum Extensibility {
153    /// `final` — no extending, byte-compatible with strict IDL templates.
154    Final,
155    /// `appendable` — spec default; only extensible at the end.
156    Appendable,
157    /// `mutable` — XCDR2 with member IDs, reordering allowed.
158    Mutable,
159}
160
161impl Default for Extensibility {
162    fn default() -> Self {
163        Self::Appendable
164    }
165}
166
167/// Primitive type symbol per Spec §7.2.1 + §7.3.3.4.4.2.
168///
169/// Strings/WStrings optionally carry the `stringMaxLength` attribute on the
170/// member, but are represented in the TypeRef table as the unbounded variant.
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum PrimitiveType {
173    /// IDL `boolean`.
174    Boolean,
175    /// IDL `octet` (unsigned 8-bit).
176    Octet,
177    /// IDL `char` (8-bit).
178    Char,
179    /// IDL `wchar` (16-bit).
180    WChar,
181    /// IDL `short` (signed 16-bit).
182    Short,
183    /// IDL `unsigned short` / `ushort`.
184    UShort,
185    /// IDL `long` (signed 32-bit).
186    Long,
187    /// IDL `unsigned long` / `ulong`.
188    ULong,
189    /// IDL `long long` / `longlong`.
190    LongLong,
191    /// IDL `unsigned long long` / `ulonglong`.
192    ULongLong,
193    /// IDL `float` (32-bit IEEE 754).
194    Float,
195    /// IDL `double` (64-bit IEEE 754).
196    Double,
197    /// IDL `long double` (extended precision).
198    LongDouble,
199    /// IDL `string` (8-bit chars).
200    String,
201    /// IDL `wstring` (16-bit chars).
202    WString,
203}
204
205impl PrimitiveType {
206    /// Parses a primitive type symbol from the `type=…` attribute of the
207    /// `<member>`/`<typedef>` elements.
208    #[must_use]
209    pub fn from_xml(s: &str) -> Option<Self> {
210        match s {
211            "boolean" => Some(Self::Boolean),
212            "octet" | "byte" => Some(Self::Octet),
213            "char" => Some(Self::Char),
214            "wchar" => Some(Self::WChar),
215            "short" | "int16" => Some(Self::Short),
216            "ushort" | "uint16" => Some(Self::UShort),
217            "long" | "int32" => Some(Self::Long),
218            "ulong" | "uint32" => Some(Self::ULong),
219            "longlong" | "int64" => Some(Self::LongLong),
220            "ulonglong" | "uint64" => Some(Self::ULongLong),
221            "float" | "float32" => Some(Self::Float),
222            "double" | "float64" => Some(Self::Double),
223            "longdouble" | "float128" => Some(Self::LongDouble),
224            "string" => Some(Self::String),
225            "wstring" => Some(Self::WString),
226            _ => None,
227        }
228    }
229
230    /// Canonical primitive type symbol (for round-trip serialization).
231    #[must_use]
232    pub fn as_xml(self) -> &'static str {
233        match self {
234            Self::Boolean => "boolean",
235            Self::Octet => "octet",
236            Self::Char => "char",
237            Self::WChar => "wchar",
238            Self::Short => "short",
239            Self::UShort => "ushort",
240            Self::Long => "long",
241            Self::ULong => "ulong",
242            Self::LongLong => "longlong",
243            Self::ULongLong => "ulonglong",
244            Self::Float => "float",
245            Self::Double => "double",
246            Self::LongDouble => "longdouble",
247            Self::String => "string",
248            Self::WString => "wstring",
249        }
250    }
251}
252
253/// Type reference from a `type=…` attribute (member, typedef, bitfield
254/// mask): either a primitive symbol or a named reference to a
255/// user-defined type (`MyModule::State`).
256#[derive(Debug, Clone, PartialEq, Eq)]
257pub enum TypeRef {
258    /// Primitive (see [`PrimitiveType`]).
259    Primitive(PrimitiveType),
260    /// Named (e.g. `MyEnum`, `MyModule::State`).
261    Named(String),
262}
263
264/// `<struct>` definition (Spec §7.3.3.4.4).
265#[derive(Debug, Clone, Default, PartialEq, Eq)]
266pub struct StructType {
267    /// Struct name (attribute `name`).
268    pub name: String,
269    /// Optional extensibility mode.
270    pub extensibility: Option<Extensibility>,
271    /// Optional base struct (attribute `baseType` / `base_type`).
272    pub base_type: Option<String>,
273    /// Members in document order.
274    pub members: Vec<StructMember>,
275}
276
277/// A single struct member (`<member …/>` — Spec §7.3.3.4.4.1).
278#[derive(Debug, Clone, PartialEq, Eq)]
279pub struct StructMember {
280    /// Member name.
281    pub name: String,
282    /// Type reference.
283    pub type_ref: TypeRef,
284    /// `@key` annotation (attribute `key="true"`).
285    pub key: bool,
286    /// `@optional` annotation (attribute `optional="true"`).
287    pub optional: bool,
288    /// `@must_understand` annotation.
289    pub must_understand: bool,
290    /// Optional member-ID override for XCDR2 (attribute `id`).
291    pub id: Option<u32>,
292    /// `stringMaxLength` attribute (bounded string/WString limit).
293    pub string_max_length: Option<u32>,
294    /// `sequenceMaxLength` attribute (bounded sequence limit).
295    pub sequence_max_length: Option<u32>,
296    /// `arrayDimensions` attribute, parsed from `"3,4"` -> `vec![3,4]`.
297    /// Empty if not an array.
298    pub array_dimensions: Vec<u32>,
299}
300
301impl Default for StructMember {
302    fn default() -> Self {
303        Self {
304            name: String::new(),
305            type_ref: TypeRef::Primitive(PrimitiveType::Long),
306            key: false,
307            optional: false,
308            must_understand: false,
309            id: None,
310            string_max_length: None,
311            sequence_max_length: None,
312            array_dimensions: Vec::new(),
313        }
314    }
315}
316
317/// `<enum>`-Definition (Spec §7.3.3.4.5).
318#[derive(Debug, Clone, Default, PartialEq, Eq)]
319pub struct EnumType {
320    /// Enum-Name.
321    pub name: String,
322    /// Optionales `bitBound`-Attribut (default 32).
323    pub bit_bound: Option<u32>,
324    /// Enumerator entries.
325    pub enumerators: Vec<EnumLiteral>,
326}
327
328/// Single `<enumerator>` entry.
329#[derive(Debug, Clone, Default, PartialEq, Eq)]
330pub struct EnumLiteral {
331    /// Symbolischer Name.
332    pub name: String,
333    /// Numeric value; `None` means implicit auto-numbering
334    /// (previous + 1, starting at 0).
335    pub value: Option<i32>,
336}
337
338/// `<union>`-Definition (Spec §7.3.3.4.6).
339#[derive(Debug, Clone, PartialEq, Eq)]
340pub struct UnionType {
341    /// Union-Name.
342    pub name: String,
343    /// Discriminator type reference (e.g. `long`, `short`, `MyEnum`).
344    pub discriminator: TypeRef,
345    /// Cases in document order.
346    pub cases: Vec<UnionCase>,
347}
348
349impl Default for UnionType {
350    fn default() -> Self {
351        Self {
352            name: String::new(),
353            discriminator: TypeRef::Primitive(PrimitiveType::Long),
354            cases: Vec::new(),
355        }
356    }
357}
358
359/// `<case>` entry (Spec §7.3.3.4.6.1).
360#[derive(Debug, Clone, Default, PartialEq, Eq)]
361pub struct UnionCase {
362    /// Discriminator values for this case.
363    /// Multiple `<caseDiscriminator>` children possible.
364    pub discriminators: Vec<UnionDiscriminator>,
365    /// Member selected when the discriminator is active.
366    pub member: StructMember,
367}
368
369/// Discriminator value of a case.
370#[derive(Debug, Clone, PartialEq, Eq)]
371pub enum UnionDiscriminator {
372    /// Numeric literal (string-encoded from the XML).
373    Value(String),
374    /// `default` branch (Spec §7.3.3.4.6.1.2).
375    Default,
376}
377
378/// `<typedef>` definition (Spec §7.3.3.4.7).
379#[derive(Debug, Clone, PartialEq, Eq)]
380pub struct TypedefType {
381    /// Typedef name.
382    pub name: String,
383    /// Aliased type.
384    pub type_ref: TypeRef,
385    /// `arrayDimensions` (empty if not an array).
386    pub array_dimensions: Vec<u32>,
387    /// `sequenceMaxLength` if it is a sequence alias.
388    pub sequence_max_length: Option<u32>,
389    /// `stringMaxLength` if it is a bounded string.
390    pub string_max_length: Option<u32>,
391}
392
393impl Default for TypedefType {
394    fn default() -> Self {
395        Self {
396            name: String::new(),
397            type_ref: TypeRef::Primitive(PrimitiveType::Long),
398            array_dimensions: Vec::new(),
399            sequence_max_length: None,
400            string_max_length: None,
401        }
402    }
403}
404
405/// `<bitmask>`-Definition (Spec §7.3.3.4.8).
406#[derive(Debug, Clone, Default, PartialEq, Eq)]
407pub struct BitmaskType {
408    /// Bitmask-Name.
409    pub name: String,
410    /// `bitBound`-Attribut (default 32).
411    pub bit_bound: Option<u32>,
412    /// `<bit_value>` entries.
413    pub bit_values: Vec<BitValue>,
414}
415
416/// Single `<bit_value>` entry.
417#[derive(Debug, Clone, Default, PartialEq, Eq)]
418pub struct BitValue {
419    /// Symbolischer Name.
420    pub name: String,
421    /// Bit position (0-based). `None` -> implicit (previous + 1).
422    pub position: Option<u32>,
423}
424
425/// `<bitset>`-Definition (Spec §7.3.3.4.9).
426#[derive(Debug, Clone, Default, PartialEq, Eq)]
427pub struct BitsetType {
428    /// Bitset-Name.
429    pub name: String,
430    /// `<bitfield>` entries.
431    pub bit_fields: Vec<BitField>,
432}
433
434/// Single `<bitfield>` element of a bitset.
435#[derive(Debug, Clone, PartialEq, Eq)]
436pub struct BitField {
437    /// Feldname.
438    pub name: String,
439    /// Underlying Type.
440    pub type_ref: TypeRef,
441    /// Bitmask as a string (e.g. `"0x06"`); we preserve the exact token
442    /// to keep round-trips stable.
443    pub mask: String,
444}
445
446impl Default for BitField {
447    fn default() -> Self {
448        Self {
449            name: String::new(),
450            type_ref: TypeRef::Primitive(PrimitiveType::ULong),
451            mask: String::new(),
452        }
453    }
454}
455
456#[cfg(test)]
457#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
458mod tests {
459    use super::*;
460
461    #[test]
462    fn primitive_type_roundtrip() {
463        for p in [
464            PrimitiveType::Boolean,
465            PrimitiveType::Octet,
466            PrimitiveType::Char,
467            PrimitiveType::WChar,
468            PrimitiveType::Short,
469            PrimitiveType::UShort,
470            PrimitiveType::Long,
471            PrimitiveType::ULong,
472            PrimitiveType::LongLong,
473            PrimitiveType::ULongLong,
474            PrimitiveType::Float,
475            PrimitiveType::Double,
476            PrimitiveType::LongDouble,
477            PrimitiveType::String,
478            PrimitiveType::WString,
479        ] {
480            let s = p.as_xml();
481            assert_eq!(PrimitiveType::from_xml(s), Some(p));
482        }
483    }
484
485    #[test]
486    fn primitive_aliases() {
487        assert_eq!(PrimitiveType::from_xml("int32"), Some(PrimitiveType::Long));
488        assert_eq!(PrimitiveType::from_xml("byte"), Some(PrimitiveType::Octet));
489        assert_eq!(
490            PrimitiveType::from_xml("uint16"),
491            Some(PrimitiveType::UShort)
492        );
493    }
494
495    #[test]
496    fn extensibility_default_appendable() {
497        assert_eq!(Extensibility::default(), Extensibility::Appendable);
498    }
499
500    #[test]
501    fn typedef_name_lookup() {
502        let lib = TypeLibrary {
503            name: "L".into(),
504            types: alloc::vec![TypeDef::Typedef(TypedefType {
505                name: "Velocity".into(),
506                ..Default::default()
507            })],
508        };
509        assert!(lib.find("Velocity").is_some());
510        assert!(lib.find("Missing").is_none());
511    }
512}