Skip to main content

zerodds_types/dynamic/
descriptor.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! TypeDescriptor + MemberDescriptor (XTypes 1.3 §7.5.1, §7.5.2).
4//!
5//! Ein `TypeDescriptor` beschreibt einen DynamicType vollstaendig: kind +
6//! Name + Bound + Element-Type usw. Er ist der **konstruktive** Eingang
7//! zu `DynamicTypeBuilderFactory::create_type` (Spec §7.5.5.1).
8//!
9//! Ein `MemberDescriptor` beschreibt einen Member innerhalb eines
10//! Composite-Types (Struct/Union/Annotation). Spec §7.5.2 listet alle
11//! Felder, die hier 1:1 abgebildet sind. Apply-Logik fuer
12//! `try_construct` (DISCARD/USE_DEFAULT/TRIM) wird in C4.7 ergaenzt.
13
14use alloc::boxed::Box;
15use alloc::string::String;
16use alloc::vec::Vec;
17
18/// XTypes 1.3 TypeKind-Enum (§7.5.1 Table 10).
19///
20/// Deckt die 24 in der Spec genannten Kinds. `NoType` entspricht
21/// `TK_NONE`.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[non_exhaustive]
24pub enum TypeKind {
25    /// Kein Typ — Sentinel-Wert.
26    NoType,
27    /// `boolean`.
28    Boolean,
29    /// `octet` / `byte` (8-bit unsigned).
30    Byte,
31    /// `int8`.
32    Int8,
33    /// `uint8`.
34    UInt8,
35    /// `int16`.
36    Int16,
37    /// `uint16`.
38    UInt16,
39    /// `int32`.
40    Int32,
41    /// `uint32`.
42    UInt32,
43    /// `int64`.
44    Int64,
45    /// `uint64`.
46    UInt64,
47    /// `float32`.
48    Float32,
49    /// `float64`.
50    Float64,
51    /// `float128` (long double).
52    Float128,
53    /// `char` (8-bit).
54    Char8,
55    /// `wchar` (16-bit).
56    Char16,
57    /// `string<N>`.
58    String8,
59    /// `wstring<N>`.
60    String16,
61    /// Enumeration.
62    Enumeration,
63    /// Bitmask.
64    Bitmask,
65    /// Alias / typedef.
66    Alias,
67    /// Array `T[D1,D2,...]`.
68    Array,
69    /// `sequence<T,N>`.
70    Sequence,
71    /// `map<K,V,N>`.
72    Map,
73    /// `struct`.
74    Structure,
75    /// `union`.
76    Union,
77    /// `bitset`.
78    Bitset,
79    /// `annotation`.
80    Annotation,
81}
82
83impl TypeKind {
84    /// `true` wenn der Kind ein primitiver, atomarer Typ ist (kein
85    /// Composite, keine Collection). Spec §7.5.1.
86    #[must_use]
87    pub const fn is_primitive(self) -> bool {
88        matches!(
89            self,
90            Self::Boolean
91                | Self::Byte
92                | Self::Int8
93                | Self::UInt8
94                | Self::Int16
95                | Self::UInt16
96                | Self::Int32
97                | Self::UInt32
98                | Self::Int64
99                | Self::UInt64
100                | Self::Float32
101                | Self::Float64
102                | Self::Float128
103                | Self::Char8
104                | Self::Char16
105        )
106    }
107
108    /// `true` wenn dieser Kind Members tragen kann (Struct/Union/
109    /// Annotation/Bitset/Bitmask/Enum).
110    #[must_use]
111    pub const fn is_aggregable(self) -> bool {
112        matches!(
113            self,
114            Self::Structure
115                | Self::Union
116                | Self::Annotation
117                | Self::Bitset
118                | Self::Bitmask
119                | Self::Enumeration
120        )
121    }
122}
123
124/// Extensibility-Kind (§7.2.2.4).
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum ExtensibilityKind {
127    /// `@final` — Typ ist abgeschlossen.
128    Final,
129    /// `@appendable` — neue Felder am Ende erlaubt (Default).
130    Appendable,
131    /// `@mutable` — beliebige Evolution mit `@id`-Bindings.
132    Mutable,
133}
134
135impl Default for ExtensibilityKind {
136    fn default() -> Self {
137        Self::Appendable
138    }
139}
140
141/// Try-Construct-Strategie (Spec §7.5.2 + §7.6.4).
142///
143/// Apply-Semantik (was passiert bei Decoder-Fehlschlag) wird in C4.7
144/// implementiert — hier ist nur das Enum + Member-Feld.
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum TryConstructKind {
147    /// Werfe das Sample weg.
148    Discard,
149    /// Setze auf den Default-Wert.
150    UseDefault,
151    /// Truncate auf Bound (Strings/Sequences).
152    Trim,
153}
154
155impl Default for TryConstructKind {
156    fn default() -> Self {
157        Self::Discard
158    }
159}
160
161/// `MemberId` — konsistent mit XTypes 1.3 §7.3.1.1 (32 bits).
162pub type MemberId = u32;
163
164/// XTypes §7.5.1.2 TypeDescriptor.
165///
166/// Beschreibt einen DynamicType — zur Konstruktion via
167/// [`crate::dynamic::DynamicTypeBuilderFactory::create_type`] oder als
168/// Read-only-Sicht via [`crate::dynamic::DynamicType::descriptor`].
169///
170/// Felder, die fuer einen gegebenen Kind irrelevant sind, koennen leer
171/// bleiben (z.B. `bound` fuer Struct = `Vec::new()`).
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct TypeDescriptor {
174    /// TypeKind.
175    pub kind: TypeKind,
176    /// Voll qualifizierter Name, z.B. `"::sensors::Chatter"`.
177    pub name: String,
178    /// Base-Type fuer Inheritance (Struct/Union).
179    pub base_type: Option<Box<TypeDescriptor>>,
180    /// Discriminator-Type fuer `kind == Union` (Pflicht).
181    pub discriminator_type: Option<Box<TypeDescriptor>>,
182    /// Bound — Array-Dimensionen, oder `[max]` fuer Sequence/String/Map.
183    /// Leer fuer Composite/Primitive.
184    pub bound: Vec<u32>,
185    /// Element-Type fuer Array/Sequence/Map.
186    pub element_type: Option<Box<TypeDescriptor>>,
187    /// Key-Type fuer Map.
188    pub key_element_type: Option<Box<TypeDescriptor>>,
189    /// Extensibility-Kind (relevant fuer Struct/Union).
190    pub extensibility_kind: ExtensibilityKind,
191    /// `@nested` — Type ist nicht als Top-Level-Topic gedacht.
192    pub is_nested: bool,
193}
194
195impl TypeDescriptor {
196    /// Konstruiert einen primitiven Descriptor.
197    #[must_use]
198    pub fn primitive(kind: TypeKind, name: impl Into<String>) -> Self {
199        Self {
200            kind,
201            name: name.into(),
202            base_type: None,
203            discriminator_type: None,
204            bound: Vec::new(),
205            element_type: None,
206            key_element_type: None,
207            extensibility_kind: ExtensibilityKind::default(),
208            is_nested: false,
209        }
210    }
211
212    /// Konstruiert einen Struct-Descriptor.
213    #[must_use]
214    pub fn structure(name: impl Into<String>) -> Self {
215        Self {
216            kind: TypeKind::Structure,
217            name: name.into(),
218            base_type: None,
219            discriminator_type: None,
220            bound: Vec::new(),
221            element_type: None,
222            key_element_type: None,
223            extensibility_kind: ExtensibilityKind::default(),
224            is_nested: false,
225        }
226    }
227
228    /// Konstruiert einen Union-Descriptor.
229    #[must_use]
230    pub fn union(name: impl Into<String>, discriminator: TypeDescriptor) -> Self {
231        Self {
232            kind: TypeKind::Union,
233            name: name.into(),
234            base_type: None,
235            discriminator_type: Some(Box::new(discriminator)),
236            bound: Vec::new(),
237            element_type: None,
238            key_element_type: None,
239            extensibility_kind: ExtensibilityKind::default(),
240            is_nested: false,
241        }
242    }
243
244    /// Konstruiert einen Sequence-Descriptor.
245    #[must_use]
246    pub fn sequence(name: impl Into<String>, element: TypeDescriptor, max: u32) -> Self {
247        Self {
248            kind: TypeKind::Sequence,
249            name: name.into(),
250            base_type: None,
251            discriminator_type: None,
252            bound: alloc::vec![max],
253            element_type: Some(Box::new(element)),
254            key_element_type: None,
255            extensibility_kind: ExtensibilityKind::default(),
256            is_nested: false,
257        }
258    }
259
260    /// Konstruiert einen Array-Descriptor.
261    #[must_use]
262    pub fn array(name: impl Into<String>, element: TypeDescriptor, dims: Vec<u32>) -> Self {
263        Self {
264            kind: TypeKind::Array,
265            name: name.into(),
266            base_type: None,
267            discriminator_type: None,
268            bound: dims,
269            element_type: Some(Box::new(element)),
270            key_element_type: None,
271            extensibility_kind: ExtensibilityKind::default(),
272            is_nested: false,
273        }
274    }
275
276    /// Konstruiert einen Map-Descriptor.
277    #[must_use]
278    pub fn map(
279        name: impl Into<String>,
280        key: TypeDescriptor,
281        element: TypeDescriptor,
282        max: u32,
283    ) -> Self {
284        Self {
285            kind: TypeKind::Map,
286            name: name.into(),
287            base_type: None,
288            discriminator_type: None,
289            bound: alloc::vec![max],
290            element_type: Some(Box::new(element)),
291            key_element_type: Some(Box::new(key)),
292            extensibility_kind: ExtensibilityKind::default(),
293            is_nested: false,
294        }
295    }
296
297    /// Konstruiert einen String-Descriptor (`string<bound>`).
298    #[must_use]
299    pub fn string8(bound: u32) -> Self {
300        Self {
301            kind: TypeKind::String8,
302            name: alloc::format!("string<{bound}>"),
303            base_type: None,
304            discriminator_type: None,
305            bound: alloc::vec![bound],
306            element_type: None,
307            key_element_type: None,
308            extensibility_kind: ExtensibilityKind::default(),
309            is_nested: false,
310        }
311    }
312
313    /// Konstruiert einen WString-Descriptor.
314    #[must_use]
315    pub fn string16(bound: u32) -> Self {
316        Self {
317            kind: TypeKind::String16,
318            name: alloc::format!("wstring<{bound}>"),
319            base_type: None,
320            discriminator_type: None,
321            bound: alloc::vec![bound],
322            element_type: None,
323            key_element_type: None,
324            extensibility_kind: ExtensibilityKind::default(),
325            is_nested: false,
326        }
327    }
328
329    /// Konstruiert einen Enum-Descriptor.
330    #[must_use]
331    pub fn enumeration(name: impl Into<String>) -> Self {
332        Self {
333            kind: TypeKind::Enumeration,
334            name: name.into(),
335            base_type: None,
336            discriminator_type: None,
337            bound: Vec::new(),
338            element_type: None,
339            key_element_type: None,
340            extensibility_kind: ExtensibilityKind::default(),
341            is_nested: false,
342        }
343    }
344
345    /// Validierung — Spec §7.5.1.4 `is_consistent()`.
346    ///
347    /// Prueft die in der Spec festgelegten Constraints fuer ein
348    /// Descriptor-Object: Discriminator-Pflicht bei Union, Bound-Pflicht
349    /// bei Array/Sequence/String/Map etc.
350    ///
351    /// # Errors
352    /// `String` mit menschen-lesbarer Fehlerbeschreibung.
353    pub fn is_consistent(&self) -> Result<(), String> {
354        // Cycle-Check: ein Descriptor darf sich nicht selbst als
355        // base_type oder element_type referenzieren (durch struktur-
356        // gleichheit erkannt — robusten Cycle-Check macht der Builder).
357        if self.name.is_empty() && self.kind != TypeKind::NoType {
358            return Err(String::from("descriptor without name"));
359        }
360        match self.kind {
361            TypeKind::Union => {
362                let Some(d) = &self.discriminator_type else {
363                    return Err(String::from("union without discriminator_type"));
364                };
365                if !is_valid_discriminator(d.kind) {
366                    return Err(alloc::format!(
367                        "union discriminator must be int/enum/bool, got {:?}",
368                        d.kind
369                    ));
370                }
371            }
372            TypeKind::Array => {
373                if self.bound.is_empty() {
374                    return Err(String::from("array without dimensions"));
375                }
376                if self.bound.contains(&0) {
377                    return Err(String::from("array dimension must be > 0"));
378                }
379                if self.element_type.is_none() {
380                    return Err(String::from("array without element_type"));
381                }
382            }
383            TypeKind::Sequence | TypeKind::String8 | TypeKind::String16 => {
384                if self.bound.len() != 1 {
385                    return Err(String::from("sequence/string needs exactly 1 bound"));
386                }
387                if matches!(self.kind, TypeKind::Sequence) && self.element_type.is_none() {
388                    return Err(String::from("sequence without element_type"));
389                }
390            }
391            TypeKind::Map => {
392                if self.bound.len() != 1 {
393                    return Err(String::from("map needs exactly 1 bound"));
394                }
395                if self.element_type.is_none() {
396                    return Err(String::from("map without value element_type"));
397                }
398                if self.key_element_type.is_none() {
399                    return Err(String::from("map without key_element_type"));
400                }
401            }
402            _ => {}
403        }
404        // Inheritance-Cycle-Check (1 Ebene; tiefere Ebenen werden im
405        // Builder via `build()` gegen die finale DynamicType gecheckt).
406        if let Some(b) = &self.base_type {
407            if b.name == self.name && !self.name.is_empty() {
408                return Err(String::from("inheritance cycle: base_type == self"));
409            }
410        }
411        Ok(())
412    }
413}
414
415/// XTypes §7.5.2.2 MemberDescriptor.
416///
417/// Beschreibt einen Member innerhalb eines Composite-Types (Struct,
418/// Union, Annotation, Bitset, Bitmask). Fuer Bitmask repraesentiert
419/// `member_type` typisch `Boolean`, `id` ist die Bit-Position.
420#[derive(Debug, Clone, PartialEq, Eq)]
421pub struct MemberDescriptor {
422    /// Member-Name (case-sensitive, eindeutig im Composite).
423    pub name: String,
424    /// Member-Id (eindeutig im Composite, fuer XCDR2).
425    pub id: MemberId,
426    /// Type des Members.
427    pub member_type: Box<TypeDescriptor>,
428    /// Default-Wert in canonical IDL-Literal-Form.
429    pub default_value: Option<String>,
430    /// Reihenfolgen-Index (0-basiert) — fuer `member_by_index`.
431    pub index: u32,
432    /// Union-Case-Labels. Spec §7.5.2 — nur fuer Union belegt.
433    pub label: Vec<i64>,
434    /// Try-Construct-Strategie (Apply in C4.7).
435    pub try_construct: TryConstructKind,
436    /// `@key` — Member ist Teil des Topic-Keys.
437    pub is_key: bool,
438    /// `@optional`.
439    pub is_optional: bool,
440    /// `@must_understand`.
441    pub is_must_understand: bool,
442    /// `@external` — indirect storage (shared_ptr in C++).
443    pub is_shared: bool,
444    /// `default:` Branch fuer Union.
445    pub is_default_label: bool,
446}
447
448impl MemberDescriptor {
449    /// Erstellt einen MemberDescriptor mit den verbreitetsten Defaults
450    /// fuer Struct-Members.
451    #[must_use]
452    pub fn new(name: impl Into<String>, id: MemberId, ty: TypeDescriptor) -> Self {
453        Self {
454            name: name.into(),
455            id,
456            member_type: Box::new(ty),
457            default_value: None,
458            index: 0,
459            label: Vec::new(),
460            try_construct: TryConstructKind::default(),
461            is_key: false,
462            is_optional: false,
463            is_must_understand: false,
464            is_shared: false,
465            is_default_label: false,
466        }
467    }
468
469    /// Validierung — Spec §7.5.2.4 `is_consistent()`.
470    ///
471    /// # Errors
472    /// `String` mit Fehlertext.
473    pub fn is_consistent(&self) -> Result<(), String> {
474        if self.name.is_empty() {
475            return Err(String::from("member without name"));
476        }
477        self.member_type.is_consistent()?;
478        if self.is_default_label && !self.label.is_empty() {
479            return Err(String::from(
480                "member with is_default_label must not have explicit labels",
481            ));
482        }
483        Ok(())
484    }
485}
486
487/// True, wenn der TypeKind ein gueltiger Union-Discriminator ist
488/// (Spec §7.4.1.4.4: integral oder enum oder boolean oder char).
489const fn is_valid_discriminator(kind: TypeKind) -> bool {
490    matches!(
491        kind,
492        TypeKind::Boolean
493            | TypeKind::Byte
494            | TypeKind::Int8
495            | TypeKind::UInt8
496            | TypeKind::Int16
497            | TypeKind::UInt16
498            | TypeKind::Int32
499            | TypeKind::UInt32
500            | TypeKind::Int64
501            | TypeKind::UInt64
502            | TypeKind::Char8
503            | TypeKind::Char16
504            | TypeKind::Enumeration
505    )
506}
507
508#[cfg(test)]
509#[allow(clippy::unwrap_used)]
510mod tests {
511    use super::*;
512
513    #[test]
514    fn type_kind_primitive_set_matches_spec_table_10() {
515        for k in [
516            TypeKind::Boolean,
517            TypeKind::Byte,
518            TypeKind::Int8,
519            TypeKind::UInt8,
520            TypeKind::Int16,
521            TypeKind::UInt16,
522            TypeKind::Int32,
523            TypeKind::UInt32,
524            TypeKind::Int64,
525            TypeKind::UInt64,
526            TypeKind::Float32,
527            TypeKind::Float64,
528            TypeKind::Float128,
529            TypeKind::Char8,
530            TypeKind::Char16,
531        ] {
532            assert!(k.is_primitive(), "{k:?} should be primitive");
533        }
534        for k in [
535            TypeKind::Structure,
536            TypeKind::Union,
537            TypeKind::Sequence,
538            TypeKind::Array,
539            TypeKind::Map,
540            TypeKind::String8,
541            TypeKind::String16,
542            TypeKind::Alias,
543        ] {
544            assert!(!k.is_primitive(), "{k:?} should not be primitive");
545        }
546    }
547
548    #[test]
549    fn type_kind_aggregable_set() {
550        assert!(TypeKind::Structure.is_aggregable());
551        assert!(TypeKind::Union.is_aggregable());
552        assert!(TypeKind::Annotation.is_aggregable());
553        assert!(TypeKind::Bitset.is_aggregable());
554        assert!(TypeKind::Bitmask.is_aggregable());
555        assert!(TypeKind::Enumeration.is_aggregable());
556        assert!(!TypeKind::Int32.is_aggregable());
557        assert!(!TypeKind::Sequence.is_aggregable());
558    }
559
560    #[test]
561    fn descriptor_struct_passes_consistency() {
562        let s = TypeDescriptor::structure("::Foo");
563        assert!(s.is_consistent().is_ok());
564    }
565
566    #[test]
567    fn descriptor_union_without_discriminator_fails() {
568        let mut u = TypeDescriptor::structure("::U");
569        u.kind = TypeKind::Union;
570        let err = u.is_consistent().unwrap_err();
571        assert!(err.contains("discriminator"));
572    }
573
574    #[test]
575    fn descriptor_union_with_invalid_discriminator_fails() {
576        let bad_disc = TypeDescriptor::structure("::S");
577        let u = TypeDescriptor::union("::U", bad_disc);
578        let err = u.is_consistent().unwrap_err();
579        assert!(err.contains("discriminator"));
580    }
581
582    #[test]
583    fn descriptor_array_without_dims_fails() {
584        let mut a = TypeDescriptor::array(
585            "::A",
586            TypeDescriptor::primitive(TypeKind::Int32, "int32"),
587            alloc::vec![3, 3],
588        );
589        a.bound.clear();
590        let err = a.is_consistent().unwrap_err();
591        assert!(err.contains("dimensions"));
592    }
593
594    #[test]
595    fn descriptor_array_with_zero_dim_fails() {
596        let a = TypeDescriptor::array(
597            "::A",
598            TypeDescriptor::primitive(TypeKind::Int32, "int32"),
599            alloc::vec![3, 0, 4],
600        );
601        let err = a.is_consistent().unwrap_err();
602        assert!(err.contains("> 0"));
603    }
604
605    #[test]
606    fn descriptor_sequence_with_element_passes() {
607        let s = TypeDescriptor::sequence(
608            "::S",
609            TypeDescriptor::primitive(TypeKind::Int32, "int32"),
610            100,
611        );
612        assert!(s.is_consistent().is_ok());
613    }
614
615    #[test]
616    fn descriptor_map_requires_both_key_and_value() {
617        let mut m = TypeDescriptor::map(
618            "::M",
619            TypeDescriptor::string8(64),
620            TypeDescriptor::primitive(TypeKind::Int64, "int64"),
621            500,
622        );
623        assert!(m.is_consistent().is_ok());
624        m.key_element_type = None;
625        assert!(m.is_consistent().is_err());
626    }
627
628    #[test]
629    fn descriptor_inheritance_cycle_self_reference_rejected() {
630        let mut s = TypeDescriptor::structure("::Foo");
631        let cycle = TypeDescriptor::structure("::Foo");
632        s.base_type = Some(Box::new(cycle));
633        let err = s.is_consistent().unwrap_err();
634        assert!(err.contains("cycle"));
635    }
636
637    #[test]
638    fn member_descriptor_default_label_with_labels_rejected() {
639        let mut m =
640            MemberDescriptor::new("x", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"));
641        m.is_default_label = true;
642        m.label = alloc::vec![0];
643        let err = m.is_consistent().unwrap_err();
644        assert!(err.contains("default_label"));
645    }
646
647    #[test]
648    fn member_descriptor_empty_name_rejected() {
649        let m = MemberDescriptor::new("", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"));
650        assert!(m.is_consistent().is_err());
651    }
652
653    #[test]
654    fn try_construct_default_is_discard() {
655        assert_eq!(TryConstructKind::default(), TryConstructKind::Discard);
656    }
657
658    #[test]
659    fn extensibility_default_is_appendable() {
660        assert_eq!(ExtensibilityKind::default(), ExtensibilityKind::Appendable);
661    }
662}