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    /// Convenience wrapper for structs.
134    ///
135    /// # Errors
136    /// See [`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 — finalizes the builder.
149    ///
150    /// Validations:
151    /// - all member descriptors consistent
152    /// - inheritance cycle via names
153    /// - union: discriminator + at least 1 case
154    /// - unique labels in a 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 names — robust for the common case.
168        if let Some(b) = &self.descriptor.base_type {
169            check_inheritance_chain(&self.descriptor.name, b)?;
170        }
171        // Union-specific 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 through the base_type chain — if a name appears twice, it is a
209/// cycle. Depth is capped at 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/// Constructs a `DynamicTypeInner` from a `TypeDescriptor` (no
235/// add_member cycle — members are derived recursively from
236/// `descriptor.bound`/`element_type`/`key_element_type`, but are not in
237/// the `members` vec, because that only applies to composite types with
238/// named members).
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: no global caches except the primitive singleton pool,
252/// which is lazily initialized via `OnceLock`.
253pub struct DynamicTypeBuilderFactory;
254
255impl DynamicTypeBuilderFactory {
256    /// Spec §7.5.5.1.1 `create_type(descriptor)`.
257    ///
258    /// # Errors
259    /// `Inconsistent` if the descriptor is 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 variant: creates a struct builder directly.
268    #[must_use]
269    pub fn create_struct(name: impl Into<String>) -> DynamicTypeBuilder {
270        DynamicTypeBuilder::new(TypeDescriptor::structure(name))
271    }
272
273    /// Convenience variant: creates a union builder directly with the
274    /// given discriminator type.
275    ///
276    /// # Errors
277    /// `Inconsistent` if the discriminator is not permitted.
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    /// Repeated calls with the same `kind` return the same
289    /// `DynamicType` instance (same `Arc` pointer).
290    ///
291    /// # Errors
292    /// `IllegalOperation` if `kind` is not a primitive.
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        // Defensive fallback for non-primitive kinds: build anew on each
352        // call — the singleton property only holds for primitives, as
353        // the spec caller in `get_primitive_type` validates.
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 path: no OnceLock — we build anew each time. The singleton
379    // property is thus structural (same content) instead of
380    // identity-based.
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        // same 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}