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}