Skip to main content

zerodds_types/dynamic/
builtin_types.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! XTypes 1.3 §7.6.5 + Annex E — Built-in Types Set (C4.4).
4//!
5//! Spec definiert vier vorregistrierte Common-Types, die jede Spec-
6//! konforme DDS-Implementation als Topic-Type nutzen koennen MUSS:
7//!
8//! ```idl
9//! @nested
10//! struct DDS::String {
11//!     string value;          // unbounded
12//! };
13//!
14//! @nested
15//! struct DDS::KeyedString {
16//!     @key string key;       // Topic-Key
17//!     string value;
18//! };
19//!
20//! @nested
21//! struct DDS::Bytes {
22//!     sequence<octet> value; // unbounded
23//! };
24//!
25//! @nested
26//! struct DDS::KeyedBytes {
27//!     @key string key;
28//!     sequence<octet> value;
29//! };
30//! ```
31//!
32//! Anwendungsfall: Cross-Vendor-Demos und Tutorials (Cyclone DDS,
33//! FastDDS, RTI Connext) registrieren diese Types per Default. Ohne
34//! sie laesst sich `IDLPub <topic-name> "Hello"` nicht ohne Custom-
35//! Type-Definition starten.
36//!
37//! Die Types werden hier als Singleton-Funktionen exposed — der Caller
38//! `register_type` rueft sie auf, sobald ein DDS-Participant enabled
39//! wird (Spec §7.6.5).
40
41extern crate alloc;
42use alloc::boxed::Box;
43use alloc::string::String;
44
45use super::builder::DynamicTypeBuilder;
46use super::descriptor::{MemberDescriptor, TypeDescriptor, TypeKind};
47use super::error::DynamicError;
48use super::type_::DynamicType;
49
50/// Spec-Type-Name `"DDS::String"` (Spec §7.6.5).
51pub const NAME_DDS_STRING: &str = "DDS::String";
52
53/// Spec-Type-Name `"DDS::KeyedString"`.
54pub const NAME_DDS_KEYED_STRING: &str = "DDS::KeyedString";
55
56/// Spec-Type-Name `"DDS::Bytes"`.
57pub const NAME_DDS_BYTES: &str = "DDS::Bytes";
58
59/// Spec-Type-Name `"DDS::KeyedBytes"`.
60pub const NAME_DDS_KEYED_BYTES: &str = "DDS::KeyedBytes";
61
62/// `DDS::String` — Spec §7.6.5 (Annex E).
63///
64/// `@nested struct DDS::String { string value; };`
65///
66/// # Errors
67/// Build-Fehler wenn die DynamicTypeBuilder-API streikt — sollte fuer
68/// diesen statisch definierten Type nie passieren.
69pub fn dds_string() -> Result<DynamicType, DynamicError> {
70    let mut desc = TypeDescriptor::structure(NAME_DDS_STRING);
71    desc.is_nested = true;
72    let mut builder = DynamicTypeBuilder::new(desc);
73    builder.add_member(MemberDescriptor::new("value", 1, unbounded_string()))?;
74    builder.build()
75}
76
77fn unbounded_string() -> TypeDescriptor {
78    let mut t = TypeDescriptor::primitive(TypeKind::String8, "string");
79    // Spec §7.5.1.2.4: bound = 0 = unbounded.
80    t.bound = alloc::vec![0];
81    t
82}
83
84fn unbounded_octet_sequence() -> TypeDescriptor {
85    let mut t = TypeDescriptor::primitive(TypeKind::Sequence, "sequence");
86    t.bound = alloc::vec![0];
87    t.element_type = Some(Box::new(TypeDescriptor::primitive(TypeKind::Byte, "octet")));
88    t
89}
90
91/// `DDS::KeyedString` — Spec §7.6.5 (Annex E).
92///
93/// `@nested struct DDS::KeyedString { @key string key; string value; };`
94///
95/// # Errors
96/// siehe [`dds_string`].
97pub fn dds_keyed_string() -> Result<DynamicType, DynamicError> {
98    let mut desc = TypeDescriptor::structure(NAME_DDS_KEYED_STRING);
99    desc.is_nested = true;
100    let mut builder = DynamicTypeBuilder::new(desc);
101    let mut key = MemberDescriptor::new("key", 1, unbounded_string());
102    key.is_key = true;
103    builder.add_member(key)?;
104    builder.add_member(MemberDescriptor::new("value", 2, unbounded_string()))?;
105    builder.build()
106}
107
108/// `DDS::Bytes` — Spec §7.6.5 (Annex E).
109///
110/// `@nested struct DDS::Bytes { sequence<octet> value; };`
111///
112/// # Errors
113/// siehe [`dds_string`].
114pub fn dds_bytes() -> Result<DynamicType, DynamicError> {
115    let mut desc = TypeDescriptor::structure(NAME_DDS_BYTES);
116    desc.is_nested = true;
117    let mut builder = DynamicTypeBuilder::new(desc);
118    builder.add_member(MemberDescriptor::new(
119        "value",
120        1,
121        unbounded_octet_sequence(),
122    ))?;
123    builder.build()
124}
125
126/// `DDS::KeyedBytes` — Spec §7.6.5 (Annex E).
127///
128/// `@nested struct DDS::KeyedBytes { @key string key; sequence<octet> value; };`
129///
130/// # Errors
131/// siehe [`dds_string`].
132pub fn dds_keyed_bytes() -> Result<DynamicType, DynamicError> {
133    let mut desc = TypeDescriptor::structure(NAME_DDS_KEYED_BYTES);
134    desc.is_nested = true;
135    let mut builder = DynamicTypeBuilder::new(desc);
136    let mut key = MemberDescriptor::new("key", 1, unbounded_string());
137    key.is_key = true;
138    builder.add_member(key)?;
139    builder.add_member(MemberDescriptor::new(
140        "value",
141        2,
142        unbounded_octet_sequence(),
143    ))?;
144    builder.build()
145}
146
147/// Liefert alle 4 Builtin-Types in Spec-Reihenfolge. Convenience-Helper
148/// fuer `Participant::enable()`-Pfade die alle Builtin-Types
149/// gleichzeitig registrieren.
150///
151/// # Errors
152/// siehe [`dds_string`].
153pub fn all_builtin_types() -> Result<[(String, DynamicType); 4], DynamicError> {
154    Ok([
155        (String::from(NAME_DDS_STRING), dds_string()?),
156        (String::from(NAME_DDS_KEYED_STRING), dds_keyed_string()?),
157        (String::from(NAME_DDS_BYTES), dds_bytes()?),
158        (String::from(NAME_DDS_KEYED_BYTES), dds_keyed_bytes()?),
159    ])
160}
161
162/// Discriminator: ist `name` einer der 4 Builtin-Type-Spec-Namen?
163#[must_use]
164pub fn is_builtin_type_name(name: &str) -> bool {
165    matches!(
166        name,
167        NAME_DDS_STRING | NAME_DDS_KEYED_STRING | NAME_DDS_BYTES | NAME_DDS_KEYED_BYTES
168    )
169}
170
171#[cfg(test)]
172#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn spec_names_match_annex_e() {
178        // Spec §7.6.5 — diese Strings duerfen NIE driften, sonst sind
179        // Cyclone-/FastDDS-Topic-Match unmoeglich.
180        assert_eq!(NAME_DDS_STRING, "DDS::String");
181        assert_eq!(NAME_DDS_KEYED_STRING, "DDS::KeyedString");
182        assert_eq!(NAME_DDS_BYTES, "DDS::Bytes");
183        assert_eq!(NAME_DDS_KEYED_BYTES, "DDS::KeyedBytes");
184    }
185
186    #[test]
187    fn dds_string_has_one_value_member() {
188        let t = dds_string().unwrap();
189        assert_eq!(t.name(), NAME_DDS_STRING);
190        assert_eq!(t.kind(), TypeKind::Structure);
191        assert_eq!(t.member_count(), 1);
192        let member = t.member_by_name("value").unwrap();
193        assert_eq!(member.dynamic_type().kind(), TypeKind::String8);
194        assert!(!member.descriptor().is_key);
195        assert!(t.descriptor().is_nested);
196    }
197
198    #[test]
199    fn dds_keyed_string_has_key_and_value() {
200        let t = dds_keyed_string().unwrap();
201        assert_eq!(t.name(), NAME_DDS_KEYED_STRING);
202        assert_eq!(t.member_count(), 2);
203        let key = t.member_by_name("key").unwrap();
204        assert!(key.descriptor().is_key);
205        assert_eq!(key.dynamic_type().kind(), TypeKind::String8);
206        let value = t.member_by_name("value").unwrap();
207        assert!(!value.descriptor().is_key);
208        assert_eq!(value.dynamic_type().kind(), TypeKind::String8);
209    }
210
211    #[test]
212    fn dds_bytes_has_unbounded_octet_sequence() {
213        let t = dds_bytes().unwrap();
214        assert_eq!(t.name(), NAME_DDS_BYTES);
215        assert_eq!(t.member_count(), 1);
216        let value = t.member_by_name("value").unwrap();
217        assert_eq!(value.dynamic_type().kind(), TypeKind::Sequence);
218        // Spec §7.5.1.2.4: bound = 0 = unbounded.
219        assert_eq!(value.dynamic_type().descriptor().bound, alloc::vec![0]);
220    }
221
222    #[test]
223    fn dds_keyed_bytes_has_key_and_octet_sequence() {
224        let t = dds_keyed_bytes().unwrap();
225        assert_eq!(t.name(), NAME_DDS_KEYED_BYTES);
226        assert_eq!(t.member_count(), 2);
227        assert!(t.member_by_name("key").unwrap().descriptor().is_key);
228        assert_eq!(
229            t.member_by_name("value").unwrap().dynamic_type().kind(),
230            TypeKind::Sequence
231        );
232    }
233
234    #[test]
235    fn all_builtin_types_returns_four_with_correct_names() {
236        let types = all_builtin_types().unwrap();
237        let names: alloc::vec::Vec<_> = types.iter().map(|(n, _)| n.as_str()).collect();
238        assert_eq!(
239            names,
240            [
241                "DDS::String",
242                "DDS::KeyedString",
243                "DDS::Bytes",
244                "DDS::KeyedBytes"
245            ]
246        );
247    }
248
249    #[test]
250    fn is_builtin_type_name_recognises_all_four() {
251        assert!(is_builtin_type_name("DDS::String"));
252        assert!(is_builtin_type_name("DDS::KeyedString"));
253        assert!(is_builtin_type_name("DDS::Bytes"));
254        assert!(is_builtin_type_name("DDS::KeyedBytes"));
255        assert!(!is_builtin_type_name("dds::string"));
256        assert!(!is_builtin_type_name("MyApp::Custom"));
257    }
258
259    #[test]
260    fn all_builtin_types_have_nested_annotation() {
261        // Spec §7.6.5: alle 4 sind `@nested` (nicht als Top-Level-Topic
262        // gedacht, aber als Member-Type oder Topic-via-Wrap usable).
263        for (name, t) in all_builtin_types().unwrap() {
264            assert!(
265                t.descriptor().is_nested,
266                "{name} should have is_nested=true"
267            );
268        }
269    }
270
271    #[test]
272    fn dds_string_singleton_calls_produce_equal_types() {
273        // Zwei separate Aufrufe liefern logisch equivalente Types.
274        let a = dds_string().unwrap();
275        let b = dds_string().unwrap();
276        assert!(a.equals(&b));
277    }
278
279    #[test]
280    fn keyed_types_have_exactly_one_key_member() {
281        for (name, t) in [
282            (NAME_DDS_KEYED_STRING, dds_keyed_string().unwrap()),
283            (NAME_DDS_KEYED_BYTES, dds_keyed_bytes().unwrap()),
284        ] {
285            let key_count = (0..t.member_count())
286                .filter_map(|i| t.member_by_index(i))
287                .filter(|m| m.descriptor().is_key)
288                .count();
289            assert_eq!(key_count, 1, "{name}: expected exactly 1 key member");
290        }
291    }
292
293    #[test]
294    fn unkeyed_types_have_no_key_member() {
295        for (name, t) in [
296            (NAME_DDS_STRING, dds_string().unwrap()),
297            (NAME_DDS_BYTES, dds_bytes().unwrap()),
298        ] {
299            let key_count = (0..t.member_count())
300                .filter_map(|i| t.member_by_index(i))
301                .filter(|m| m.descriptor().is_key)
302                .count();
303            assert_eq!(key_count, 0, "{name}: expected no key members");
304        }
305    }
306}