Skip to main content

zerodds_types/dynamic/
collection.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Collection (`sequence`/`array`) `DynamicType`s that retain a **fully
4//! resolved** element [`DynamicType`].
5//!
6//! The public `TypeDescriptor` keeps only a shallow element descriptor
7//! (`element_type: Box<TypeDescriptor>`) — enough for scalar elements, but it
8//! drops the members of a *composite* element (`sequence<Struct>` etc.), which
9//! the reflective codec needs to recurse. Rather than widen the core type model
10//! (and the TypeObject bridge that builds on it), this module constructs the
11//! collection `DynamicType` with the resolved element kept as its single
12//! synthetic member (index 0). [`resolved_element`] reads it back; the codec
13//! prefers it and falls back to rebuilding a scalar element from the descriptor
14//! when absent (so collections built the plain way still decode).
15
16use alloc::vec;
17
18use super::descriptor::{MemberDescriptor, TypeDescriptor, TypeKind};
19use super::type_::{DynamicType, DynamicTypeInner, DynamicTypeMember};
20
21/// The synthetic member name under which the resolved element type is stored.
22const ELEMENT_MEMBER: &str = "@element";
23
24/// Build a `sequence<Element>` type (bound `0` = unbounded) that retains the
25/// fully-resolved `element` type for the reflective codec.
26#[must_use]
27pub fn sequence_of(element: DynamicType, bound: u32) -> DynamicType {
28    sequence_named("seq", element, bound)
29}
30
31/// Like [`sequence_of`] but keeps a caller-supplied type name (e.g. a named
32/// `typedef sequence<T> Foo` from a TypeObject) instead of the anonymous `"seq"`.
33#[must_use]
34pub fn sequence_named(name: &str, element: DynamicType, bound: u32) -> DynamicType {
35    let desc = TypeDescriptor::sequence(name, element.descriptor().clone(), bound);
36    with_element(desc, element)
37}
38
39/// Build an `Element[dims...]` array type that retains the fully-resolved
40/// `element` type.
41#[must_use]
42pub fn array_of(element: DynamicType, dims: alloc::vec::Vec<u32>, name: &str) -> DynamicType {
43    let desc = TypeDescriptor::array(name, element.descriptor().clone(), dims);
44    with_element(desc, element)
45}
46
47fn with_element(desc: TypeDescriptor, element: DynamicType) -> DynamicType {
48    let mut md = MemberDescriptor::new(ELEMENT_MEMBER, 0, element.descriptor().clone());
49    md.index = 0;
50    DynamicType::from_inner(DynamicTypeInner {
51        descriptor: desc,
52        members: vec![DynamicTypeMember {
53            descriptor: md,
54            member_type: element,
55        }],
56    })
57}
58
59/// The synthetic member names under which a map's resolved key + value types are
60/// stored (index 0 = key, index 1 = value).
61const KEY_MEMBER: &str = "@key";
62const VALUE_MEMBER: &str = "@value";
63
64/// Build a `map<Key, Value>` type retaining BOTH fully-resolved key and value
65/// [`DynamicType`]s (key at index 0, value at index 1) for the reflective codec.
66#[must_use]
67pub fn map_of(key: DynamicType, value: DynamicType, bound: u32, name: &str) -> DynamicType {
68    let desc = TypeDescriptor::map(
69        name,
70        key.descriptor().clone(),
71        value.descriptor().clone(),
72        bound,
73    );
74    let mut km = MemberDescriptor::new(KEY_MEMBER, 0, key.descriptor().clone());
75    km.index = 0;
76    let mut vm = MemberDescriptor::new(VALUE_MEMBER, 1, value.descriptor().clone());
77    vm.index = 1;
78    DynamicType::from_inner(DynamicTypeInner {
79        descriptor: desc,
80        members: vec![
81            DynamicTypeMember {
82                descriptor: km,
83                member_type: key,
84            },
85            DynamicTypeMember {
86                descriptor: vm,
87                member_type: value,
88            },
89        ],
90    })
91}
92
93/// The resolved element [`DynamicType`] of a collection, if one was attached via
94/// [`sequence_of`] / [`array_of`]. Returns `None` for collections that carry only
95/// a shallow scalar element descriptor (the codec rebuilds those itself).
96#[must_use]
97pub fn resolved_element(ty: &DynamicType) -> Option<&DynamicType> {
98    if matches!(ty.kind(), TypeKind::Sequence | TypeKind::Array) {
99        ty.member_by_index(0).map(|m| m.dynamic_type())
100    } else {
101        None
102    }
103}
104
105/// The resolved key type of a `map<K,V>` built via [`map_of`], if present.
106#[must_use]
107pub fn resolved_map_key(ty: &DynamicType) -> Option<&DynamicType> {
108    if ty.kind() == TypeKind::Map {
109        ty.member_by_index(0).map(|m| m.dynamic_type())
110    } else {
111        None
112    }
113}
114
115/// The resolved value type of a `map<K,V>` built via [`map_of`], if present.
116#[must_use]
117pub fn resolved_map_value(ty: &DynamicType) -> Option<&DynamicType> {
118    if ty.kind() == TypeKind::Map {
119        ty.member_by_index(1).map(|m| m.dynamic_type())
120    } else {
121        None
122    }
123}
124
125#[cfg(test)]
126#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
127mod tests {
128    use super::*;
129    use crate::dynamic::builder::DynamicTypeBuilderFactory as F;
130
131    #[test]
132    fn sequence_of_struct_retains_element() {
133        let mut b = F::create_struct("E");
134        b.add_struct_member("a", 0, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
135            .unwrap();
136        let elem = b.build().unwrap();
137        let seq = sequence_of(elem, 0);
138        assert_eq!(seq.kind(), TypeKind::Sequence);
139        let re = resolved_element(&seq).expect("resolved element");
140        assert_eq!(re.kind(), TypeKind::Structure);
141        assert_eq!(re.member_count(), 1);
142    }
143
144    #[test]
145    fn scalar_sequence_has_no_resolved_element() {
146        // A plain sequence descriptor (no synthetic member) → None.
147        let desc = TypeDescriptor::sequence(
148            "seq",
149            TypeDescriptor::primitive(TypeKind::Int32, "int32"),
150            0,
151        );
152        let ty = F::create_type(desc).unwrap().build().unwrap();
153        assert!(resolved_element(&ty).is_none());
154    }
155}