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}