Skip to main content

zerodds_xml/
conformance.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Conformance markers for DDS-XML 1.0 §2.4 + §7.2.0.
4//!
5//! § 2.4 — *Atomic building-block selection.* The spec requires that the
6//! implementation can atomically state per building block: "selected"
7//! or "not selected". We mark that via
8//! [`SUPPORTED_BUILDING_BLOCKS`] — the list of the building blocks
9//! productively supported by the crate. An `assert!`-based
10//! test table (`tests::supported_blocks_match_repo`) verifies
11//! that the list matches the actually exposed modules.
12//!
13//! § 7.2.0 — *1-to-1 mapping of IDL data types.* The spec says: "The XML
14//! representation of resources that correspond to data-types defined
15//! in the DDS IDL PSM is obtained by performing a 1-to-1 mapping of
16//! the corresponding IDL data type." We encode this mapping
17//! table as [`IDL_TO_XML_MAPPING`] — per IDL data type category
18//! a reference to the producing function or module, so that
19//! reviewers/testers can see the completeness of the coverage in one
20//! place.
21
22extern crate alloc;
23
24/// List of the building blocks from DDS-XML 1.0 §7.3.1.1 that are
25/// productively supported in this crate.
26///
27/// Spec §7.3.1.1: "This specification breaks the syntax used to
28/// represent DDS resources in XML into the six different building
29/// blocks: Building Block QoS, Types, Domains, DomainParticipants,
30/// Applications, Data Samples."
31///
32/// Per entry: `(spec_name, module_name, top_level_element)`.
33pub const SUPPORTED_BUILDING_BLOCKS: &[(&str, &str, &str)] = &[
34    ("QoS", "qos", "qos_library"),
35    ("Types", "xtypes_def", "types"),
36    ("Domains", "domain", "domain_library"),
37    (
38        "DomainParticipants",
39        "participant",
40        "domain_participant_library",
41    ),
42    ("Applications", "application", "application_library"),
43    ("DataSamples", "sample", "data"),
44];
45
46/// 1-to-1 mapping IDL data type -> XML constructor + producing
47/// API function. Spec §7.2.0.
48///
49/// Per entry: `(idl_category, spec_section, repo_path)`.
50pub const IDL_TO_XML_MAPPING: &[(&str, &str, &str)] = &[
51    (
52        "boolean",
53        "§7.1.4 Tab.7.1",
54        "types::parse_bool / parse_bool_strict",
55    ),
56    (
57        "long (32-bit signed)",
58        "§7.1.4 Tab.7.1",
59        "types::parse_long",
60    ),
61    (
62        "unsigned long (32-bit)",
63        "§7.1.4 Tab.7.1",
64        "types::parse_ulong",
65    ),
66    ("string", "§7.1.4 Tab.7.1", "types::parse_string"),
67    ("enum", "§7.1.4 Tab.7.1 / §7.2.1", "types::parse_enum"),
68    ("LENGTH_UNLIMITED", "§7.2.2.1", "types::LENGTH_UNLIMITED"),
69    (
70        "DURATION_INFINITE_SEC/NSEC",
71        "§7.2.2.2 / §7.2.2.3",
72        "types::DURATION_INFINITE_SEC / DURATION_INFINITE_NSEC",
73    ),
74    (
75        "DURATION_ZERO_SEC/NSEC",
76        "§7.2.2.4 / §7.2.2.5",
77        "types::DURATION_ZERO_SEC / DURATION_ZERO_NSEC",
78    ),
79    (
80        "nonNegativeInteger_UNLIMITED",
81        "§7.2.2.8",
82        "types::parse_long (Number-or-Symbol)",
83    ),
84    (
85        "positiveInteger_UNLIMITED",
86        "§7.2.2.9",
87        "types::parse_positive_long_unlimited",
88    ),
89    (
90        "nonNegativeInteger_Duration_SEC",
91        "§7.2.2.10",
92        "types::parse_duration_sec",
93    ),
94    (
95        "nonNegativeInteger_Duration_NSEC",
96        "§7.2.2.11",
97        "types::parse_duration_nsec",
98    ),
99    ("struct (IDL)", "§7.2.3", "qos_parser::* (recursive)"),
100    (
101        "sequence<T> (IDL)",
102        "§7.2.4.1",
103        "parser::XmlElement::sequence_elements",
104    ),
105    (
106        "sequence<octet> (IDL)",
107        "§7.2.4.2",
108        "types::parse_octet_sequence + qos_parser::base64_decode",
109    ),
110    (
111        "T[N] (IDL Array)",
112        "§7.2.5",
113        "parser::XmlElement::sequence_elements (re-use)",
114    ),
115    ("Duration_t", "§7.2.6", "qos_parser::parse_duration"),
116];
117
118#[cfg(test)]
119#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
120mod tests {
121    use super::*;
122    use alloc::collections::BTreeSet;
123
124    #[test]
125    fn supported_blocks_match_spec_count() {
126        // Spec §7.3.1.1 names **6** building blocks — that is exactly
127        // our list.
128        assert_eq!(SUPPORTED_BUILDING_BLOCKS.len(), 6);
129    }
130
131    #[test]
132    fn supported_blocks_have_unique_modules() {
133        let mut seen = BTreeSet::new();
134        for (name, module, _root) in SUPPORTED_BUILDING_BLOCKS {
135            assert!(
136                seen.insert(*module),
137                "module `{module}` registered twice for block `{name}`"
138            );
139        }
140    }
141
142    #[test]
143    fn supported_blocks_have_unique_root_elements() {
144        let mut seen = BTreeSet::new();
145        for (name, _module, root) in SUPPORTED_BUILDING_BLOCKS {
146            assert!(
147                seen.insert(*root),
148                "top-level element `{root}` duplicated for block `{name}`"
149            );
150        }
151    }
152
153    #[test]
154    fn idl_mapping_covers_required_categories() {
155        // Sanity: the mapping table contains at least all categories
156        // from §7.1.4 Tab.7.1 (boolean, enum, long, ulong, string) +
157        // §7.2.x (sequences, arrays, Duration).
158        let names: BTreeSet<&str> = IDL_TO_XML_MAPPING
159            .iter()
160            .map(|(name, _, _)| *name)
161            .collect();
162        for required in [
163            "boolean",
164            "long (32-bit signed)",
165            "unsigned long (32-bit)",
166            "string",
167            "enum",
168            "Duration_t",
169        ] {
170            assert!(
171                names.contains(required),
172                "mapping table missing entry for `{required}`"
173            );
174        }
175    }
176
177    #[test]
178    fn idl_mapping_entries_unique() {
179        let mut seen = BTreeSet::new();
180        for (name, _, _) in IDL_TO_XML_MAPPING {
181            assert!(seen.insert(*name), "mapping entry `{name}` duplicated");
182        }
183    }
184
185    #[test]
186    fn idl_mapping_includes_section_7_2_x_items() {
187        // §7.2.x items that are fully live after K7-A must appear
188        // in the table — otherwise §7.2.0 cannot be "done".
189        let sections: BTreeSet<&str> = IDL_TO_XML_MAPPING.iter().map(|(_, sec, _)| *sec).collect();
190        for required in ["§7.2.2.9", "§7.2.4.1", "§7.2.4.2", "§7.2.5", "§7.2.6"] {
191            assert!(
192                sections.contains(required),
193                "mapping table missing § section `{required}`"
194            );
195        }
196    }
197}