Skip to main content

zerodds_types/dynamic/
builder.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! DynamicTypeBuilder + DynamicTypeBuilderFactory (XTypes 1.3 §7.5.4, §7.5.5).
4//!
5//! Spec-Verhalten:
6//! - `add_member` validiert Name und Id sofort gegen schon vorhandene
7//!   Members (Spec §7.5.4.1.2 Preconditions).
8//! - `build()` validiert die finale Struktur (Inheritance-Cycle,
9//!   Discriminator-Pflicht, etc.) und liefert eine immutable
10//!   `DynamicType`.
11
12use alloc::collections::BTreeMap;
13use alloc::string::{String, ToString};
14use alloc::sync::Arc;
15use alloc::vec::Vec;
16use core::sync::atomic::{AtomicBool, Ordering};
17
18use super::descriptor::{MemberDescriptor, MemberId, TypeDescriptor, TypeKind};
19use super::error::DynamicError;
20use super::type_::{DynamicType, DynamicTypeInner, DynamicTypeMember, primitive_name};
21
22/// XTypes §7.5.4 DynamicTypeBuilder.
23#[derive(Debug)]
24pub struct DynamicTypeBuilder {
25    descriptor: TypeDescriptor,
26    members: Vec<DynamicTypeMember>,
27    sealed: AtomicBool,
28}
29
30impl DynamicTypeBuilder {
31    /// Internal — die Factory erzeugt Builder.
32    pub(super) fn new(descriptor: TypeDescriptor) -> Self {
33        Self {
34            descriptor,
35            members: Vec::new(),
36            sealed: AtomicBool::new(false),
37        }
38    }
39
40    /// Aktueller Descriptor (read-only-View).
41    #[must_use]
42    pub fn descriptor(&self) -> &TypeDescriptor {
43        &self.descriptor
44    }
45
46    /// Setzt den Descriptor neu (Spec §7.5.4.1 SetDescriptor) — nur
47    /// erlaubt vor `build()`.
48    ///
49    /// # Errors
50    /// `PreconditionNotMet` wenn schon `build()` aufgerufen wurde.
51    pub fn set_descriptor(&mut self, descriptor: TypeDescriptor) -> Result<(), DynamicError> {
52        if self.sealed.load(Ordering::Acquire) {
53            return Err(DynamicError::PreconditionNotMet(String::from(
54                "set_descriptor after build()",
55            )));
56        }
57        descriptor
58            .is_consistent()
59            .map_err(DynamicError::inconsistent)?;
60        self.descriptor = descriptor;
61        Ok(())
62    }
63
64    /// Fuegt einen Member hinzu (Spec §7.5.4.1.2 AddMember).
65    ///
66    /// Validiert sofort:
67    /// - Eindeutiger Name unter den bisherigen Members.
68    /// - Eindeutige Id (nur wenn Composite XCDR2-tauglich).
69    /// - Member-Typ ist konsistent.
70    /// - Kind erlaubt Members.
71    ///
72    /// `index` wird automatisch gesetzt, falls vom Caller auf 0
73    /// gelassen, sonst respektiert.
74    ///
75    /// # Errors
76    /// `BuilderConflict` bei Dup-Name/Id, `IllegalOperation` wenn der
77    /// Kind keine Members traegt.
78    pub fn add_member(&mut self, mut descriptor: MemberDescriptor) -> Result<(), DynamicError> {
79        if self.sealed.load(Ordering::Acquire) {
80            return Err(DynamicError::PreconditionNotMet(String::from(
81                "add_member after build()",
82            )));
83        }
84        if !self.descriptor.kind.is_aggregable() {
85            return Err(DynamicError::IllegalOperation(alloc::format!(
86                "add_member on non-composite kind {:?}",
87                self.descriptor.kind
88            )));
89        }
90        descriptor
91            .is_consistent()
92            .map_err(DynamicError::inconsistent)?;
93
94        // Dup-Name-Check.
95        if self
96            .members
97            .iter()
98            .any(|m| m.descriptor.name == descriptor.name)
99        {
100            return Err(DynamicError::builder(alloc::format!(
101                "duplicate member name {}",
102                descriptor.name
103            )));
104        }
105        // Dup-Id-Check.
106        if self
107            .members
108            .iter()
109            .any(|m| m.descriptor.id == descriptor.id)
110        {
111            return Err(DynamicError::builder(alloc::format!(
112                "duplicate member id {}",
113                descriptor.id
114            )));
115        }
116        // Auto-Index falls Caller index=0 fuer alle laesst (default-Pattern).
117        let auto_index = u32::try_from(self.members.len()).unwrap_or(u32::MAX);
118        if descriptor.index == 0 && auto_index != 0 {
119            descriptor.index = auto_index;
120        } else if descriptor.index == 0 {
121            descriptor.index = 0; // erster Member bleibt 0
122        }
123        // Member-Type bauen.
124        let member_type =
125            DynamicType::from_inner(descriptor_to_dynamic_type_inner(&descriptor.member_type)?);
126        self.members.push(DynamicTypeMember {
127            descriptor,
128            member_type,
129        });
130        Ok(())
131    }
132
133    /// Convenience-Wrapper fuer Strukturen.
134    ///
135    /// # Errors
136    /// Siehe [`add_member`].
137    pub fn add_struct_member(
138        &mut self,
139        name: impl Into<String>,
140        id: MemberId,
141        ty: TypeDescriptor,
142    ) -> Result<(), DynamicError> {
143        let mut d = MemberDescriptor::new(name, id, ty);
144        d.index = u32::try_from(self.members.len()).unwrap_or(u32::MAX);
145        self.add_member(d)
146    }
147
148    /// Spec §7.5.4.1.1 Build — finalisiert den Builder.
149    ///
150    /// Validierungen:
151    /// - alle Member-Descriptors konsistent
152    /// - Inheritance-Cycle ueber Namen
153    /// - Union: Discriminator + mind. 1 Case
154    /// - Eindeutige Labels in Union
155    ///
156    /// # Errors
157    /// `BuilderConflict` / `Inconsistent`.
158    pub fn build(&self) -> Result<DynamicType, DynamicError> {
159        if self.sealed.swap(true, Ordering::AcqRel) {
160            return Err(DynamicError::PreconditionNotMet(String::from(
161                "build() called twice",
162            )));
163        }
164        self.descriptor
165            .is_consistent()
166            .map_err(DynamicError::inconsistent)?;
167        // Cycle-Check via Namen — robust fuer den common case.
168        if let Some(b) = &self.descriptor.base_type {
169            check_inheritance_chain(&self.descriptor.name, b)?;
170        }
171        // Union-spezifische Checks.
172        if self.descriptor.kind == TypeKind::Union {
173            if self.members.is_empty() {
174                return Err(DynamicError::builder(
175                    "union without case members".to_string(),
176                ));
177            }
178            let mut seen_labels: BTreeMap<i64, &str> = BTreeMap::new();
179            let mut default_count = 0_u32;
180            for m in &self.members {
181                if m.descriptor.is_default_label {
182                    default_count += 1;
183                }
184                for label in &m.descriptor.label {
185                    if let Some(prev) = seen_labels.insert(*label, &m.descriptor.name) {
186                        return Err(DynamicError::builder(alloc::format!(
187                            "duplicate union label {label} (prev member: {prev})"
188                        )));
189                    }
190                }
191            }
192            if default_count > 1 {
193                return Err(DynamicError::builder(
194                    "union with multiple default-label members",
195                ));
196            }
197        }
198        let inner = DynamicTypeInner {
199            descriptor: self.descriptor.clone(),
200            members: self.members.clone(),
201        };
202        Ok(DynamicType {
203            inner: Arc::new(inner),
204        })
205    }
206}
207
208/// Walk durch base_type-Kette — wenn ein Name doppelt vorkommt, ist es
209/// ein Cycle. Tiefe ist begrenzt auf 64 (DoS-Cap).
210fn check_inheritance_chain(self_name: &str, base: &TypeDescriptor) -> Result<(), DynamicError> {
211    let mut seen: alloc::vec::Vec<&str> = alloc::vec![self_name];
212    let mut cur = base;
213    let mut depth = 0_usize;
214    loop {
215        if depth >= 64 {
216            return Err(DynamicError::builder("inheritance chain exceeds 64 levels"));
217        }
218        if seen.iter().any(|n| *n == cur.name) {
219            return Err(DynamicError::builder(alloc::format!(
220                "inheritance cycle through '{}'",
221                cur.name
222            )));
223        }
224        seen.push(&cur.name);
225        depth += 1;
226        if let Some(b) = &cur.base_type {
227            cur = b;
228        } else {
229            return Ok(());
230        }
231    }
232}
233
234/// Konstruiert einen `DynamicTypeInner` aus einem `TypeDescriptor`
235/// (kein add_member-Cycle — Members werden aus
236/// `descriptor.bound`/`element_type`/`key_element_type` rekursiv
237/// abgeleitet, sind aber nicht im `members`-Vec, weil das nur fuer
238/// Composite-Typen mit benannten Members gilt).
239pub(super) fn descriptor_to_dynamic_type_inner(
240    desc: &TypeDescriptor,
241) -> Result<DynamicTypeInner, DynamicError> {
242    desc.is_consistent().map_err(DynamicError::inconsistent)?;
243    Ok(DynamicTypeInner {
244        descriptor: desc.clone(),
245        members: Vec::new(),
246    })
247}
248
249/// XTypes §7.5.5 DynamicTypeBuilderFactory — Singleton im Spec-Sinne.
250///
251/// Stateless: keine globalen Caches außer dem Primitive-Singleton-
252/// Pool, der über `OnceLock` lazy initialisiert wird.
253pub struct DynamicTypeBuilderFactory;
254
255impl DynamicTypeBuilderFactory {
256    /// Spec §7.5.5.1.1 `create_type(descriptor)`.
257    ///
258    /// # Errors
259    /// `Inconsistent` wenn Descriptor invalid.
260    pub fn create_type(descriptor: TypeDescriptor) -> Result<DynamicTypeBuilder, DynamicError> {
261        descriptor
262            .is_consistent()
263            .map_err(DynamicError::inconsistent)?;
264        Ok(DynamicTypeBuilder::new(descriptor))
265    }
266
267    /// Convenience-Variante: erstellt direkt einen Struct-Builder.
268    #[must_use]
269    pub fn create_struct(name: impl Into<String>) -> DynamicTypeBuilder {
270        DynamicTypeBuilder::new(TypeDescriptor::structure(name))
271    }
272
273    /// Convenience-Variante: erstellt direkt einen Union-Builder mit
274    /// gegebenem Discriminator-Type.
275    ///
276    /// # Errors
277    /// `Inconsistent` wenn der Discriminator nicht zugelassen ist.
278    pub fn create_union(
279        name: impl Into<String>,
280        discriminator: TypeDescriptor,
281    ) -> Result<DynamicTypeBuilder, DynamicError> {
282        let desc = TypeDescriptor::union(name, discriminator);
283        Self::create_type(desc)
284    }
285
286    /// Spec §7.5.5.1.2 `get_primitive_type(kind)` — Singleton-Cache.
287    ///
288    /// Mehrfach-Aufrufe mit gleichem `kind` liefern dieselbe
289    /// `DynamicType`-Instanz (gleicher `Arc`-Pointer).
290    ///
291    /// # Errors
292    /// `IllegalOperation` wenn `kind` kein Primitive ist.
293    pub fn get_primitive_type(kind: TypeKind) -> Result<DynamicType, DynamicError> {
294        if !kind.is_primitive() {
295            return Err(DynamicError::IllegalOperation(alloc::format!(
296                "get_primitive_type called with non-primitive {kind:?}"
297            )));
298        }
299        Ok(primitive_singleton(kind))
300    }
301
302    /// Spec §7.5.5.1.3 `create_string_type(bound)` — bounded `string<N>`.
303    #[must_use]
304    pub fn create_string_type(bound: u32) -> DynamicType {
305        DynamicType::from_inner(DynamicTypeInner {
306            descriptor: TypeDescriptor::string8(bound),
307            members: Vec::new(),
308        })
309    }
310
311    /// Spec §7.5.5.1.4 `create_wstring_type(bound)`.
312    #[must_use]
313    pub fn create_wstring_type(bound: u32) -> DynamicType {
314        DynamicType::from_inner(DynamicTypeInner {
315            descriptor: TypeDescriptor::string16(bound),
316            members: Vec::new(),
317        })
318    }
319}
320
321// ----------------------------------------------------------------------
322// Primitive-Singleton-Cache
323// ----------------------------------------------------------------------
324
325#[cfg(feature = "std")]
326fn primitive_singleton(kind: TypeKind) -> DynamicType {
327    use std::sync::OnceLock;
328    type Cell = OnceLock<DynamicType>;
329    macro_rules! cell {
330        () => {{
331            static C: Cell = OnceLock::new();
332            &C
333        }};
334    }
335    let cell: &Cell = match kind {
336        TypeKind::Boolean => cell!(),
337        TypeKind::Byte => cell!(),
338        TypeKind::Int8 => cell!(),
339        TypeKind::UInt8 => cell!(),
340        TypeKind::Int16 => cell!(),
341        TypeKind::UInt16 => cell!(),
342        TypeKind::Int32 => cell!(),
343        TypeKind::UInt32 => cell!(),
344        TypeKind::Int64 => cell!(),
345        TypeKind::UInt64 => cell!(),
346        TypeKind::Float32 => cell!(),
347        TypeKind::Float64 => cell!(),
348        TypeKind::Float128 => cell!(),
349        TypeKind::Char8 => cell!(),
350        TypeKind::Char16 => cell!(),
351        // Defensiver Fallback fuer nicht-primitive Kinds: bauen jeden
352        // Aufruf neu — Singleton-Eigenschaft gilt nur fuer primitives,
353        // wie der Spec-Caller in `get_primitive_type` validiert.
354        _ => {
355            return DynamicType::from_inner(DynamicTypeInner {
356                descriptor: TypeDescriptor::primitive(
357                    kind,
358                    alloc::string::String::from(primitive_name(kind)),
359                ),
360                members: Vec::new(),
361            });
362        }
363    };
364    cell.get_or_init(|| {
365        DynamicType::from_inner(DynamicTypeInner {
366            descriptor: TypeDescriptor::primitive(
367                kind,
368                alloc::string::String::from(primitive_name(kind)),
369            ),
370            members: Vec::new(),
371        })
372    })
373    .clone()
374}
375
376#[cfg(not(feature = "std"))]
377fn primitive_singleton(kind: TypeKind) -> DynamicType {
378    // no_std-Pfad: keine OnceLock — wir bauen jedes Mal neu. Damit ist
379    // die Singleton-Eigenschaft strukturell (gleicher Inhalt) statt
380    // Identity-basiert.
381    DynamicType::from_inner(DynamicTypeInner {
382        descriptor: TypeDescriptor::primitive(
383            kind,
384            alloc::string::String::from(primitive_name(kind)),
385        ),
386        members: Vec::new(),
387    })
388}
389
390#[cfg(test)]
391#[allow(clippy::unwrap_used)]
392mod tests {
393    use super::*;
394
395    #[test]
396    fn create_type_rejects_invalid_descriptor() {
397        let mut bad = TypeDescriptor::structure("");
398        bad.kind = TypeKind::Structure;
399        let err = DynamicTypeBuilderFactory::create_type(bad).unwrap_err();
400        assert!(matches!(err, DynamicError::Inconsistent(_)));
401    }
402
403    #[test]
404    fn add_member_rejects_duplicate_name() {
405        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
406        b.add_struct_member("a", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
407            .unwrap();
408        let err = b
409            .add_struct_member("a", 2, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
410            .unwrap_err();
411        assert!(matches!(err, DynamicError::BuilderConflict(_)));
412    }
413
414    #[test]
415    fn add_member_rejects_duplicate_id() {
416        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
417        b.add_struct_member("a", 5, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
418            .unwrap();
419        let err = b
420            .add_struct_member("b", 5, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
421            .unwrap_err();
422        assert!(matches!(err, DynamicError::BuilderConflict(_)));
423    }
424
425    #[test]
426    fn add_member_on_primitive_is_illegal() {
427        let mut b = DynamicTypeBuilder::new(TypeDescriptor::primitive(TypeKind::Int32, "int32"));
428        let err = b
429            .add_struct_member("x", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
430            .unwrap_err();
431        assert!(matches!(err, DynamicError::IllegalOperation(_)));
432    }
433
434    #[test]
435    fn build_twice_rejected() {
436        let b = DynamicTypeBuilderFactory::create_struct("::S");
437        let _ = b.build().unwrap();
438        let err = b.build().unwrap_err();
439        assert!(matches!(err, DynamicError::PreconditionNotMet(_)));
440    }
441
442    #[test]
443    fn primitive_singleton_returns_same_arc() {
444        let a = DynamicTypeBuilderFactory::get_primitive_type(TypeKind::Int32).unwrap();
445        let b = DynamicTypeBuilderFactory::get_primitive_type(TypeKind::Int32).unwrap();
446        // gleicher Arc-Pointer (Singleton).
447        assert!(Arc::ptr_eq(&a.inner, &b.inner));
448    }
449
450    #[test]
451    fn primitive_singleton_rejects_non_primitive() {
452        assert!(matches!(
453            DynamicTypeBuilderFactory::get_primitive_type(TypeKind::Structure),
454            Err(DynamicError::IllegalOperation(_))
455        ));
456    }
457
458    #[test]
459    fn union_build_requires_at_least_one_member() {
460        let disc = TypeDescriptor::primitive(TypeKind::Int32, "int32");
461        let b = DynamicTypeBuilderFactory::create_union("::U", disc).unwrap();
462        let err = b.build().unwrap_err();
463        assert!(matches!(err, DynamicError::BuilderConflict(_)));
464    }
465
466    #[test]
467    fn union_duplicate_label_rejected() {
468        let disc = TypeDescriptor::primitive(TypeKind::Int32, "int32");
469        let mut b = DynamicTypeBuilderFactory::create_union("::U", disc).unwrap();
470        let mut a =
471            MemberDescriptor::new("a", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"));
472        a.label = alloc::vec![1, 2];
473        b.add_member(a).unwrap();
474        let mut c =
475            MemberDescriptor::new("c", 2, TypeDescriptor::primitive(TypeKind::Int32, "int32"));
476        c.label = alloc::vec![2, 3];
477        b.add_member(c).unwrap();
478        let err = b.build().unwrap_err();
479        assert!(matches!(err, DynamicError::BuilderConflict(_)));
480    }
481
482    #[test]
483    fn build_simple_struct_with_three_members() {
484        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
485        b.add_struct_member("a", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
486            .unwrap();
487        b.add_struct_member("b", 2, TypeDescriptor::primitive(TypeKind::Int64, "int64"))
488            .unwrap();
489        b.add_struct_member("c", 3, TypeDescriptor::string8(64))
490            .unwrap();
491        let t = b.build().unwrap();
492        assert_eq!(t.member_count(), 3);
493        assert_eq!(t.member_by_name("b").unwrap().id(), 2);
494        assert_eq!(t.member_by_id(3).unwrap().name(), "c");
495        assert_eq!(t.member_by_index(0).unwrap().name(), "a");
496    }
497}