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 behavior:
6//! - `add_member` validates name and id immediately against existing
7//!   members (spec §7.5.4.1.2 preconditions).
8//! - `build()` validates the final structure (inheritance cycle,
9//!   mandatory discriminator, etc.) and returns an 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 — the factory creates builders.
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    /// Current descriptor (read-only view).
41    #[must_use]
42    pub fn descriptor(&self) -> &TypeDescriptor {
43        &self.descriptor
44    }
45
46    /// Sets the descriptor anew (spec §7.5.4.1 SetDescriptor) — only
47    /// allowed before `build()`.
48    ///
49    /// # Errors
50    /// `PreconditionNotMet` if `build()` was already called.
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    /// Adds a member (spec §7.5.4.1.2 AddMember).
65    ///
66    /// Validates immediately:
67    /// - Unique name among the existing members.
68    /// - Unique id (only when composite XCDR2-capable).
69    /// - The member type is consistent.
70    /// - The kind allows members.
71    ///
72    /// `index` is set automatically if the caller leaves it at 0,
73    /// otherwise respected.
74    ///
75    /// # Errors
76    /// `BuilderConflict` on a dup name/id, `IllegalOperation` if the
77    /// kind carries no members.
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 if the caller leaves index=0 for all (the 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    /// Adds a member whose type is a fully-resolved `DynamicType` instead of
134    /// being (shallowly) reconstructed from `descriptor.member_type`. Used by
135    /// the TypeObject → DynamicType bridge to attach a recursively-resolved
136    /// nested composite (struct/union/enum) member type — `add_member` would
137    /// otherwise rebuild it from the member's shallow `TypeDescriptor` and lose
138    /// the nested members. Runs the same validity checks as [`add_member`].
139    ///
140    /// # Errors
141    /// `PreconditionNotMet` after `build()`, `IllegalOperation` on a
142    /// non-composite, `Inconsistent`/`Builder` on a malformed or duplicate member.
143    pub fn add_member_resolved(
144        &mut self,
145        mut descriptor: MemberDescriptor,
146        member_type: DynamicType,
147    ) -> Result<(), DynamicError> {
148        if self.sealed.load(Ordering::Acquire) {
149            return Err(DynamicError::PreconditionNotMet(String::from(
150                "add_member after build()",
151            )));
152        }
153        if !self.descriptor.kind.is_aggregable() {
154            return Err(DynamicError::IllegalOperation(alloc::format!(
155                "add_member on non-composite kind {:?}",
156                self.descriptor.kind
157            )));
158        }
159        descriptor
160            .is_consistent()
161            .map_err(DynamicError::inconsistent)?;
162        if self
163            .members
164            .iter()
165            .any(|m| m.descriptor.name == descriptor.name)
166        {
167            return Err(DynamicError::builder(alloc::format!(
168                "duplicate member name {}",
169                descriptor.name
170            )));
171        }
172        if self
173            .members
174            .iter()
175            .any(|m| m.descriptor.id == descriptor.id)
176        {
177            return Err(DynamicError::builder(alloc::format!(
178                "duplicate member id {}",
179                descriptor.id
180            )));
181        }
182        let auto_index = u32::try_from(self.members.len()).unwrap_or(u32::MAX);
183        if descriptor.index == 0 && auto_index != 0 {
184            descriptor.index = auto_index;
185        }
186        self.members.push(DynamicTypeMember {
187            descriptor,
188            member_type,
189        });
190        Ok(())
191    }
192
193    /// Convenience wrapper for structs.
194    ///
195    /// # Errors
196    /// See [`add_member`].
197    pub fn add_struct_member(
198        &mut self,
199        name: impl Into<String>,
200        id: MemberId,
201        ty: TypeDescriptor,
202    ) -> Result<(), DynamicError> {
203        let mut d = MemberDescriptor::new(name, id, ty);
204        d.index = u32::try_from(self.members.len()).unwrap_or(u32::MAX);
205        self.add_member(d)
206    }
207
208    /// Spec §7.5.4.1.1 Build — finalizes the builder.
209    ///
210    /// Validations:
211    /// - all member descriptors consistent
212    /// - inheritance cycle via names
213    /// - union: discriminator + at least 1 case
214    /// - unique labels in a union
215    ///
216    /// # Errors
217    /// `BuilderConflict` / `Inconsistent`.
218    pub fn build(&self) -> Result<DynamicType, DynamicError> {
219        if self.sealed.swap(true, Ordering::AcqRel) {
220            return Err(DynamicError::PreconditionNotMet(String::from(
221                "build() called twice",
222            )));
223        }
224        self.descriptor
225            .is_consistent()
226            .map_err(DynamicError::inconsistent)?;
227        // Cycle check via names — robust for the common case.
228        if let Some(b) = &self.descriptor.base_type {
229            check_inheritance_chain(&self.descriptor.name, b)?;
230        }
231        // Union-specific checks.
232        if self.descriptor.kind == TypeKind::Union {
233            if self.members.is_empty() {
234                return Err(DynamicError::builder(
235                    "union without case members".to_string(),
236                ));
237            }
238            let mut seen_labels: BTreeMap<i64, &str> = BTreeMap::new();
239            let mut default_count = 0_u32;
240            for m in &self.members {
241                if m.descriptor.is_default_label {
242                    default_count += 1;
243                }
244                for label in &m.descriptor.label {
245                    if let Some(prev) = seen_labels.insert(*label, &m.descriptor.name) {
246                        return Err(DynamicError::builder(alloc::format!(
247                            "duplicate union label {label} (prev member: {prev})"
248                        )));
249                    }
250                }
251            }
252            if default_count > 1 {
253                return Err(DynamicError::builder(
254                    "union with multiple default-label members",
255                ));
256            }
257        }
258        let inner = DynamicTypeInner {
259            descriptor: self.descriptor.clone(),
260            members: self.members.clone(),
261        };
262        Ok(DynamicType {
263            inner: Arc::new(inner),
264        })
265    }
266}
267
268/// Walk through the base_type chain — if a name appears twice, it is a
269/// cycle. Depth is capped at 64 (DoS cap).
270fn check_inheritance_chain(self_name: &str, base: &TypeDescriptor) -> Result<(), DynamicError> {
271    let mut seen: alloc::vec::Vec<&str> = alloc::vec![self_name];
272    let mut cur = base;
273    let mut depth = 0_usize;
274    loop {
275        if depth >= 64 {
276            return Err(DynamicError::builder("inheritance chain exceeds 64 levels"));
277        }
278        if seen.iter().any(|n| *n == cur.name) {
279            return Err(DynamicError::builder(alloc::format!(
280                "inheritance cycle through '{}'",
281                cur.name
282            )));
283        }
284        seen.push(&cur.name);
285        depth += 1;
286        if let Some(b) = &cur.base_type {
287            cur = b;
288        } else {
289            return Ok(());
290        }
291    }
292}
293
294/// Constructs a `DynamicTypeInner` from a `TypeDescriptor` (no
295/// add_member cycle — members are derived recursively from
296/// `descriptor.bound`/`element_type`/`key_element_type`, but are not in
297/// the `members` vec, because that only applies to composite types with
298/// named members).
299pub(super) fn descriptor_to_dynamic_type_inner(
300    desc: &TypeDescriptor,
301) -> Result<DynamicTypeInner, DynamicError> {
302    desc.is_consistent().map_err(DynamicError::inconsistent)?;
303    Ok(DynamicTypeInner {
304        descriptor: desc.clone(),
305        members: Vec::new(),
306    })
307}
308
309/// XTypes §7.5.5 DynamicTypeBuilderFactory — Singleton im Spec-Sinne.
310///
311/// Stateless: no global caches except the primitive singleton pool,
312/// which is lazily initialized via `OnceLock`.
313pub struct DynamicTypeBuilderFactory;
314
315impl DynamicTypeBuilderFactory {
316    /// Spec §7.5.5.1.1 `create_type(descriptor)`.
317    ///
318    /// # Errors
319    /// `Inconsistent` if the descriptor is invalid.
320    pub fn create_type(descriptor: TypeDescriptor) -> Result<DynamicTypeBuilder, DynamicError> {
321        descriptor
322            .is_consistent()
323            .map_err(DynamicError::inconsistent)?;
324        Ok(DynamicTypeBuilder::new(descriptor))
325    }
326
327    /// Convenience variant: creates a struct builder directly.
328    #[must_use]
329    pub fn create_struct(name: impl Into<String>) -> DynamicTypeBuilder {
330        DynamicTypeBuilder::new(TypeDescriptor::structure(name))
331    }
332
333    /// Convenience variant: creates a union builder directly with the
334    /// given discriminator type.
335    ///
336    /// # Errors
337    /// `Inconsistent` if the discriminator is not permitted.
338    pub fn create_union(
339        name: impl Into<String>,
340        discriminator: TypeDescriptor,
341    ) -> Result<DynamicTypeBuilder, DynamicError> {
342        let desc = TypeDescriptor::union(name, discriminator);
343        Self::create_type(desc)
344    }
345
346    /// Spec §7.5.5.1.2 `get_primitive_type(kind)` — Singleton-Cache.
347    ///
348    /// Repeated calls with the same `kind` return the same
349    /// `DynamicType` instance (same `Arc` pointer).
350    ///
351    /// # Errors
352    /// `IllegalOperation` if `kind` is not a primitive.
353    pub fn get_primitive_type(kind: TypeKind) -> Result<DynamicType, DynamicError> {
354        if !kind.is_primitive() {
355            return Err(DynamicError::IllegalOperation(alloc::format!(
356                "get_primitive_type called with non-primitive {kind:?}"
357            )));
358        }
359        Ok(primitive_singleton(kind))
360    }
361
362    /// Spec §7.5.5.1.3 `create_string_type(bound)` — bounded `string<N>`.
363    #[must_use]
364    pub fn create_string_type(bound: u32) -> DynamicType {
365        DynamicType::from_inner(DynamicTypeInner {
366            descriptor: TypeDescriptor::string8(bound),
367            members: Vec::new(),
368        })
369    }
370
371    /// Spec §7.5.5.1.4 `create_wstring_type(bound)`.
372    #[must_use]
373    pub fn create_wstring_type(bound: u32) -> DynamicType {
374        DynamicType::from_inner(DynamicTypeInner {
375            descriptor: TypeDescriptor::string16(bound),
376            members: Vec::new(),
377        })
378    }
379}
380
381// ----------------------------------------------------------------------
382// Primitive-Singleton-Cache
383// ----------------------------------------------------------------------
384
385#[cfg(feature = "std")]
386fn primitive_singleton(kind: TypeKind) -> DynamicType {
387    use std::sync::OnceLock;
388    type Cell = OnceLock<DynamicType>;
389    macro_rules! cell {
390        () => {{
391            static C: Cell = OnceLock::new();
392            &C
393        }};
394    }
395    let cell: &Cell = match kind {
396        TypeKind::Boolean => cell!(),
397        TypeKind::Byte => cell!(),
398        TypeKind::Int8 => cell!(),
399        TypeKind::UInt8 => cell!(),
400        TypeKind::Int16 => cell!(),
401        TypeKind::UInt16 => cell!(),
402        TypeKind::Int32 => cell!(),
403        TypeKind::UInt32 => cell!(),
404        TypeKind::Int64 => cell!(),
405        TypeKind::UInt64 => cell!(),
406        TypeKind::Float32 => cell!(),
407        TypeKind::Float64 => cell!(),
408        TypeKind::Float128 => cell!(),
409        TypeKind::Char8 => cell!(),
410        TypeKind::Char16 => cell!(),
411        // Defensive fallback for non-primitive kinds: build anew on each
412        // call — the singleton property only holds for primitives, as
413        // the spec caller in `get_primitive_type` validates.
414        _ => {
415            return DynamicType::from_inner(DynamicTypeInner {
416                descriptor: TypeDescriptor::primitive(
417                    kind,
418                    alloc::string::String::from(primitive_name(kind)),
419                ),
420                members: Vec::new(),
421            });
422        }
423    };
424    cell.get_or_init(|| {
425        DynamicType::from_inner(DynamicTypeInner {
426            descriptor: TypeDescriptor::primitive(
427                kind,
428                alloc::string::String::from(primitive_name(kind)),
429            ),
430            members: Vec::new(),
431        })
432    })
433    .clone()
434}
435
436#[cfg(not(feature = "std"))]
437fn primitive_singleton(kind: TypeKind) -> DynamicType {
438    // no_std path: no OnceLock — we build anew each time. The singleton
439    // property is thus structural (same content) instead of
440    // identity-based.
441    DynamicType::from_inner(DynamicTypeInner {
442        descriptor: TypeDescriptor::primitive(
443            kind,
444            alloc::string::String::from(primitive_name(kind)),
445        ),
446        members: Vec::new(),
447    })
448}
449
450#[cfg(test)]
451#[allow(clippy::unwrap_used)]
452mod tests {
453    use super::*;
454
455    #[test]
456    fn create_type_rejects_invalid_descriptor() {
457        let mut bad = TypeDescriptor::structure("");
458        bad.kind = TypeKind::Structure;
459        let err = DynamicTypeBuilderFactory::create_type(bad).unwrap_err();
460        assert!(matches!(err, DynamicError::Inconsistent(_)));
461    }
462
463    #[test]
464    fn add_member_rejects_duplicate_name() {
465        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
466        b.add_struct_member("a", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
467            .unwrap();
468        let err = b
469            .add_struct_member("a", 2, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
470            .unwrap_err();
471        assert!(matches!(err, DynamicError::BuilderConflict(_)));
472    }
473
474    #[test]
475    fn add_member_rejects_duplicate_id() {
476        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
477        b.add_struct_member("a", 5, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
478            .unwrap();
479        let err = b
480            .add_struct_member("b", 5, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
481            .unwrap_err();
482        assert!(matches!(err, DynamicError::BuilderConflict(_)));
483    }
484
485    #[test]
486    fn add_member_on_primitive_is_illegal() {
487        let mut b = DynamicTypeBuilder::new(TypeDescriptor::primitive(TypeKind::Int32, "int32"));
488        let err = b
489            .add_struct_member("x", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
490            .unwrap_err();
491        assert!(matches!(err, DynamicError::IllegalOperation(_)));
492    }
493
494    #[test]
495    fn build_twice_rejected() {
496        let b = DynamicTypeBuilderFactory::create_struct("::S");
497        let _ = b.build().unwrap();
498        let err = b.build().unwrap_err();
499        assert!(matches!(err, DynamicError::PreconditionNotMet(_)));
500    }
501
502    #[test]
503    fn primitive_singleton_returns_same_arc() {
504        let a = DynamicTypeBuilderFactory::get_primitive_type(TypeKind::Int32).unwrap();
505        let b = DynamicTypeBuilderFactory::get_primitive_type(TypeKind::Int32).unwrap();
506        // same Arc pointer (singleton).
507        assert!(Arc::ptr_eq(&a.inner, &b.inner));
508    }
509
510    #[test]
511    fn primitive_singleton_rejects_non_primitive() {
512        assert!(matches!(
513            DynamicTypeBuilderFactory::get_primitive_type(TypeKind::Structure),
514            Err(DynamicError::IllegalOperation(_))
515        ));
516    }
517
518    #[test]
519    fn union_build_requires_at_least_one_member() {
520        let disc = TypeDescriptor::primitive(TypeKind::Int32, "int32");
521        let b = DynamicTypeBuilderFactory::create_union("::U", disc).unwrap();
522        let err = b.build().unwrap_err();
523        assert!(matches!(err, DynamicError::BuilderConflict(_)));
524    }
525
526    #[test]
527    fn union_duplicate_label_rejected() {
528        let disc = TypeDescriptor::primitive(TypeKind::Int32, "int32");
529        let mut b = DynamicTypeBuilderFactory::create_union("::U", disc).unwrap();
530        let mut a =
531            MemberDescriptor::new("a", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"));
532        a.label = alloc::vec![1, 2];
533        b.add_member(a).unwrap();
534        let mut c =
535            MemberDescriptor::new("c", 2, TypeDescriptor::primitive(TypeKind::Int32, "int32"));
536        c.label = alloc::vec![2, 3];
537        b.add_member(c).unwrap();
538        let err = b.build().unwrap_err();
539        assert!(matches!(err, DynamicError::BuilderConflict(_)));
540    }
541
542    #[test]
543    fn build_simple_struct_with_three_members() {
544        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
545        b.add_struct_member("a", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
546            .unwrap();
547        b.add_struct_member("b", 2, TypeDescriptor::primitive(TypeKind::Int64, "int64"))
548            .unwrap();
549        b.add_struct_member("c", 3, TypeDescriptor::string8(64))
550            .unwrap();
551        let t = b.build().unwrap();
552        assert_eq!(t.member_count(), 3);
553        assert_eq!(t.member_by_name("b").unwrap().id(), 2);
554        assert_eq!(t.member_by_id(3).unwrap().name(), "c");
555        assert_eq!(t.member_by_index(0).unwrap().name(), "a");
556    }
557}