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" — Datenmodell.
4//!
5//! Beschreibungs-Datenmodell der XML-Sicht auf XTypes 1.3 Type-Definitionen.
6//!
7//! `zerodds-xml` haelt bewusst eine *eigenstaendige* Beschreibungsschicht und
8//! macht **keine** Cross-Crate-Edge zu `zerodds-types::DynamicType`. Hoeher
9//! liegende Adapter koennen die hier gehaltenen Strukturen spaeter in
10//! ihre DCPS-Datenmodelle uebersetzen.
11//!
12//! Spec-Quelle: 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> erlaubt)
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 fuer 1+ Type-Definitionen aus einem `<types>`-Block.
42///
43/// Spec §7.3.3.4: ein DDS-XML-Dokument darf mehrere `<types>`-Top-Level-
44/// Elemente fuehren. Jeder Block wird als eine `TypeLibrary` mit
45/// optionalem Namen (Annotation, nicht Spec-Pflicht) modelliert.
46#[derive(Debug, Clone, Default, PartialEq, Eq)]
47pub struct TypeLibrary {
48    /// Optionaler Library-Name (z.B. `<types name="Lib1">`); Spec laesst
49    /// das Attribut zu, ohne es verpflichtend zu machen.
50    pub name: String,
51    /// Type-Definitionen in Dokument-Reihenfolge. Module sind als
52    /// `TypeDef::Module(ModuleEntry)` eingebettet (nested).
53    pub types: Vec<TypeDef>,
54}
55
56impl TypeLibrary {
57    /// Lookup eines Top-Level-Types ueber seinen lokalen Namen.
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/// Ein einzelner Type-Eintrag (Spec §7.3.3.4 — Struct/Enum/Union/Typedef/
65/// Bitmask/Bitset oder geschachteltes Modul).
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 mit optionalen Array/Sequence-Modifiern.
75    Typedef(TypedefType),
76    /// `<bitmask>` — XTypes Bitmask Type.
77    Bitmask(BitmaskType),
78    /// `<bitset>` — XTypes Bitset Type.
79    Bitset(BitsetType),
80    /// `<module>` — Namespacing-Container; weitere Types nested inside.
81    Module(ModuleEntry),
82    /// `<include>` — externe XML-Datei einziehen (DDS-XML 1.0 §7.3.3.4 +
83    /// XTypes 1.3 §7.3.2). Wird beim parse als Marker erfasst; Resolver
84    /// kann ihn nachgelagert zur Composition aufloesen.
85    Include(IncludeEntry),
86    /// `<forward_dcl>` — Forward-Decl ohne Members (XTypes 1.3 §7.3.2).
87    /// Erlaubt mutual-recursive Type-Refs.
88    ForwardDcl(ForwardDeclEntry),
89    /// `<const>` — Konstanten-Definition (XTypes 1.3 §7.3.2 / IDL 4.2
90    /// §7.4.1.4.4). Wert als String; Caller konvertiert.
91    Const(ConstEntry),
92}
93
94/// `<include file="..."/>` — XML-Composition.
95#[derive(Debug, Clone, PartialEq, Eq, Default)]
96pub struct IncludeEntry {
97    /// Datei-Pfad relativ zur einschliessenden 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" oder "UNION" — Spec laesst nur diese zwei zu.
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    /// Konstanten-Name.
114    pub name: String,
115    /// Type (Primitive-String wie "long", "string", etc.).
116    pub type_name: String,
117    /// Roher Wert als String.
118    pub value: String,
119}
120
121impl TypeDef {
122    /// Lokaler Name des Types/Moduls.
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/// Geschachteltes Modul (`<module name="…">` — Spec §7.3.3.4.1).
141#[derive(Debug, Clone, Default, PartialEq, Eq)]
142pub struct ModuleEntry {
143    /// Modul-Name.
144    pub name: String,
145    /// Geschachtelte Type-Definitionen.
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` — kein Erweitern, byte-kompatibel zu strikten IDL-Vorlagen.
154    Final,
155    /// `appendable` — Spec-Default; nur am Ende erweiterbar.
156    Appendable,
157    /// `mutable` — XCDR2 mit Member-IDs, Reordering erlaubt.
158    Mutable,
159}
160
161impl Default for Extensibility {
162    fn default() -> Self {
163        Self::Appendable
164    }
165}
166
167/// Primitive-Type-Symbol gemaess Spec §7.2.1 + §7.3.3.4.4.2.
168///
169/// Strings/WStrings tragen optional `stringMaxLength`-Attribut am Member,
170/// aber sind in der TypeRef-Tabelle als unbounded-Variante dargestellt.
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    /// Parst ein Primitive-Type-Symbol aus dem `type=…`-Attribut der
207    /// `<member>`/`<typedef>`-Elemente.
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    /// Kanonisches Primitive-Type-Symbol (fuer Round-Trip-Serialisierung).
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-Verweis aus einem `type=…`-Attribut (Member, Typedef, Bitfield-
254/// Mask): entweder Primitive-Symbol oder Named-Reference auf einen
255/// nutzer-definierten Type (`MyModule::State`).
256#[derive(Debug, Clone, PartialEq, Eq)]
257pub enum TypeRef {
258    /// Primitive (siehe [`PrimitiveType`]).
259    Primitive(PrimitiveType),
260    /// Named (z.B. `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 (Attribut `name`).
268    pub name: String,
269    /// Optionaler Extensibility-Mode.
270    pub extensibility: Option<Extensibility>,
271    /// Optionaler Base-Struct (Attribut `baseType` / `base_type`).
272    pub base_type: Option<String>,
273    /// Member in Dokument-Reihenfolge.
274    pub members: Vec<StructMember>,
275}
276
277/// Einzelner 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-Verweis.
283    pub type_ref: TypeRef,
284    /// `@key`-Annotation (Attribut `key="true"`).
285    pub key: bool,
286    /// `@optional`-Annotation (Attribut `optional="true"`).
287    pub optional: bool,
288    /// `@must_understand`-Annotation.
289    pub must_understand: bool,
290    /// Optionaler Member-ID-Override fuer XCDR2 (Attribut `id`).
291    pub id: Option<u32>,
292    /// `stringMaxLength`-Attribut (Bounded-String/WString-Limit).
293    pub string_max_length: Option<u32>,
294    /// `sequenceMaxLength`-Attribut (Bounded-Sequence-Limit).
295    pub sequence_max_length: Option<u32>,
296    /// `arrayDimensions`-Attribut, parsed aus `"3,4"` -> `vec![3,4]`.
297    /// Leer wenn kein 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-Eintraege.
325    pub enumerators: Vec<EnumLiteral>,
326}
327
328/// Einzelner `<enumerator>`-Eintrag.
329#[derive(Debug, Clone, Default, PartialEq, Eq)]
330pub struct EnumLiteral {
331    /// Symbolischer Name.
332    pub name: String,
333    /// Numerischer Wert; `None` bedeutet implicit auto-numbering
334    /// (vorheriger + 1, beginnend bei 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-Verweis (z.B. `long`, `short`, `MyEnum`).
344    pub discriminator: TypeRef,
345    /// Cases in Dokument-Reihenfolge.
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>`-Eintrag (Spec §7.3.3.4.6.1).
360#[derive(Debug, Clone, Default, PartialEq, Eq)]
361pub struct UnionCase {
362    /// Discriminator-Werte fuer diesen Case.
363    /// Mehrere `<caseDiscriminator>`-Children moeglich.
364    pub discriminators: Vec<UnionDiscriminator>,
365    /// Member, der bei aktivem Discriminator gewaehlt ist.
366    pub member: StructMember,
367}
368
369/// Discriminator-Wert eines Cases.
370#[derive(Debug, Clone, PartialEq, Eq)]
371pub enum UnionDiscriminator {
372    /// Numerischer Literal (string-encoded vom 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` (leer wenn kein Array).
386    pub array_dimensions: Vec<u32>,
387    /// `sequenceMaxLength` falls Sequence-Alias.
388    pub sequence_max_length: Option<u32>,
389    /// `stringMaxLength` falls 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>`-Eintraege.
413    pub bit_values: Vec<BitValue>,
414}
415
416/// Einzelner `<bit_value>`-Eintrag.
417#[derive(Debug, Clone, Default, PartialEq, Eq)]
418pub struct BitValue {
419    /// Symbolischer Name.
420    pub name: String,
421    /// Bit-Position (0-based). `None` -> implicit (vorheriger + 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>`-Eintraege.
431    pub bit_fields: Vec<BitField>,
432}
433
434/// Einzelnes `<bitfield>`-Element eines Bitsets.
435#[derive(Debug, Clone, PartialEq, Eq)]
436pub struct BitField {
437    /// Feldname.
438    pub name: String,
439    /// Underlying Type.
440    pub type_ref: TypeRef,
441    /// Bitmaske als String (z.B. `"0x06"`); wir bewahren das exakte Token,
442    /// um Round-Trip stabil zu halten.
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}