Skip to main content

zerodds_xml/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! DDS-XML 1.0 Loader — well-formed Foundation (Cluster F).
4//!
5//! Crate `zerodds-xml`. Implements the shared well-formedness and
6//! IDL-PSM mapping layer from DDS-XML 1.0 §7.1 and §7.2. Building-block-
7//! specific decoders (QoS library, types, domains, participants,
8//! applications, samples) build on this crate — see
9//! `docs/spec-coverage/zerodds-xml-1.0.open.md` Cluster G/H/I/J.
10//!
11//! # Spec sources
12//!
13//! * OMG DDS-XML 1.0, formal/18-10-01, December 2018.
14//! * §7.1 — XML Representation Syntax (general rules + schema +
15//!   chameleon pattern).
16//! * §7.2 — XML Representation of DDS IDL PSM (data type mapping).
17//! * §7.2.2 — symbol constants (`LENGTH_UNLIMITED`, `DURATION_INFINITE_*`).
18//! * §7.2.6 — `Duration_t` representation.
19//!
20//! # Public API (foundation)
21//!
22//! * [`parse_xml_tree`] — generic well-formedness loader, returns
23//!   [`DdsXmlDocument`].
24//! * [`parse_dds_xml`] — high-level building-block loader, returns
25//!   [`DdsXml`] (Cluster G+H+I+J).
26//! * [`DdsXmlDocument`] / [`XmlElement`] — in-memory tree.
27//! * Data type helpers from [`types`] (boolean, long, ulong, Duration,
28//!   enum whitelist, string).
29//! * Inheritance resolution from [`inheritance::resolve_chain`] with
30//!   cycle detection.
31//! * Errors from [`XmlError`].
32//!
33//! Safety classification: **STANDARD**.
34//!
35//! See `docs/architecture/02_architecture.md §3` and
36//! `docs/architecture/04_safety_by_architecture.md §2`.
37//!
38//! The crate is `#![forbid(unsafe_code)]`, `no_std + alloc`, and
39//! uses exclusively `roxmltree` (Apache-2.0 / MIT, pure-Rust)
40//! as the XML backend.
41
42#![cfg_attr(not(feature = "std"), no_std)]
43#![forbid(unsafe_code)]
44#![warn(missing_docs)]
45#![cfg_attr(test, allow(clippy::panic, clippy::unwrap_used, clippy::expect_used))]
46
47extern crate alloc;
48
49#[cfg(feature = "std")]
50extern crate std;
51
52pub mod application;
53pub mod conformance;
54pub mod domain;
55pub mod errors;
56pub mod inheritance;
57pub mod parser;
58pub mod participant;
59pub mod qos;
60pub mod qos_inheritance;
61pub mod qos_parser;
62pub mod registry;
63pub mod resolver;
64pub mod sample;
65pub mod schemas;
66pub mod typeobject_bridge;
67pub mod types;
68pub mod xsd_loader;
69pub mod xsd_schema;
70pub mod xtypes_def;
71pub mod xtypes_parser;
72pub mod zerodds_xml;
73
74pub use application::{ApplicationEntry, ApplicationLibrary, parse_application_libraries};
75pub use conformance::{IDL_TO_XML_MAPPING, SUPPORTED_BUILDING_BLOCKS};
76pub use domain::{DomainEntry, DomainLibrary, RegisterType, TopicEntry, parse_domain_libraries};
77pub use errors::XmlError;
78pub use inheritance::{MAX_INHERITANCE_DEPTH, resolve_chain};
79pub use parser::{
80    DDS_XML_NS, DdsXmlDocument, MAX_LIST_ELEMENTS, MAX_TOTAL_ELEMENTS, XmlElement, parse_xml_tree,
81};
82pub use participant::{
83    DataReaderEntry, DataWriterEntry, DomainParticipantEntry, DomainParticipantLibrary,
84    PublisherEntry, SubscriberEntry, parse_domain_participant_libraries,
85};
86pub use qos::{EntityQos, QosLibrary, QosProfile, topic_filter_matches};
87pub use qos_inheritance::{ResolvedQos, resolve_profile};
88pub use qos_parser::{
89    parse_bool_strict, parse_entity_qos_public, parse_qos_libraries, parse_qos_library,
90    parse_qos_library_element_public,
91};
92pub use registry::QosProfileRegistry;
93pub use resolver::{LibraryRef, parse_library_ref};
94pub use sample::{
95    PrimitiveValue, SampleValue, parse_sample, parse_sample_element, serialize_sample,
96};
97pub use schemas::{
98    ALL_SCHEMAS, APPLICATIONS_NAMESPACED_XSD, APPLICATIONS_NONAMESPACE_XSD, COMMON_XSD,
99    DATA_SAMPLES_NAMESPACED_XSD, DATA_SAMPLES_NONAMESPACE_XSD, DDS_SYSTEM_NAMESPACED_XSD,
100    DDS_SYSTEM_NONAMESPACE_XSD, DOMAIN_PARTICIPANTS_NAMESPACED_XSD,
101    DOMAIN_PARTICIPANTS_NONAMESPACE_XSD, DOMAINS_NAMESPACED_XSD, DOMAINS_NONAMESPACE_XSD,
102    QOS_NAMESPACED_XSD, QOS_NONAMESPACE_XSD, TYPES_NAMESPACED_XSD, TYPES_NONAMESPACE_XSD,
103    embedded_block_names,
104};
105pub use typeobject_bridge::{
106    BridgeError, bridge_library, xml_type_to_minimal_typeobject, xml_type_to_typeobject,
107};
108pub use types::{
109    DURATION_INFINITE_NSEC, DURATION_INFINITE_SEC, DURATION_ZERO_NSEC, DURATION_ZERO_SEC, Duration,
110    LENGTH_UNLIMITED, MAX_STRING_BYTES, TIME_INVALID_NSEC, TIME_INVALID_SEC, parse_bool,
111    parse_duration_nsec, parse_duration_sec, parse_enum, parse_long, parse_octet_sequence,
112    parse_positive_long_unlimited, parse_string, parse_ulong,
113};
114pub use xtypes_def::{
115    BitField, BitValue, BitmaskType, BitsetType, EnumLiteral, EnumType, Extensibility, ModuleEntry,
116    PrimitiveType, StructMember, StructType, TypeDef, TypeLibrary, TypeRef, TypedefType, UnionCase,
117    UnionDiscriminator, UnionType,
118};
119pub use xtypes_parser::{parse_type_libraries, parse_types_element};
120pub use zerodds_xml::{
121    DdsXml, ParticipantFactoryAdapter, ResolvedDataReader, ResolvedDataWriter, ResolvedParticipant,
122    ResolvedPublisher, ResolvedSubscriber, ResolvedTopic, apply_to_factory, parse_dds_xml,
123};
124
125#[cfg(feature = "std")]
126pub use xsd_loader::load_type_libraries_from_uri;
127pub use xsd_loader::{
128    DDS_XML_NAMESPACE, MAX_DATA_URI_BODY, MAX_FILE_BYTES, ValidationMode,
129    load_type_libraries_from_string,
130};
131
132#[cfg(test)]
133#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
134mod integration_tests {
135    use super::*;
136    use alloc::collections::BTreeMap;
137    use alloc::string::{String, ToString};
138
139    /// End-to-end: parse a QoS-Library-shaped XML, walk the tree,
140    /// extract values via the Cluster-F type helpers.
141    #[test]
142    fn qos_profile_shape_roundtrip() {
143        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
144<dds xmlns="http://www.omg.org/spec/DDS-XML">
145    <!-- root namespace per §7.3.x targetNamespace -->
146    <qos_library name="Lib1">
147        <qos_profile name="Base">
148            <datawriter_qos>
149                <reliability>
150                    <kind>RELIABLE_RELIABILITY_QOS</kind>
151                    <max_blocking_time>
152                        <sec>DURATION_INFINITE_SEC</sec>
153                        <nanosec>DURATION_INFINITE_NSEC</nanosec>
154                    </max_blocking_time>
155                </reliability>
156                <history>
157                    <kind>KEEP_LAST_HISTORY_QOS</kind>
158                    <depth>10</depth>
159                </history>
160                <resource_limits>
161                    <max_samples>LENGTH_UNLIMITED</max_samples>
162                </resource_limits>
163            </datawriter_qos>
164        </qos_profile>
165        <qos_profile name="Derived" base_name="Base">
166        </qos_profile>
167    </qos_library>
168</dds>"#;
169        let doc = parse_xml_tree(xml).expect("parse");
170        assert_eq!(doc.root.name, "dds");
171        assert_eq!(doc.root.namespace.as_deref(), Some(DDS_XML_NS));
172
173        let lib = doc.root.child("qos_library").expect("lib");
174        assert_eq!(lib.attribute("name"), Some("Lib1"));
175
176        let profiles: alloc::vec::Vec<_> = lib.children_named("qos_profile").collect();
177        assert_eq!(profiles.len(), 2);
178
179        let base = profiles[0];
180        assert_eq!(base.attribute("name"), Some("Base"));
181        assert_eq!(base.attribute("base_name"), None);
182
183        let derived = profiles[1];
184        assert_eq!(derived.attribute("name"), Some("Derived"));
185        assert_eq!(derived.attribute("base_name"), Some("Base"));
186
187        // Reach into <reliability><max_blocking_time> and parse Duration.
188        let dw = base.child("datawriter_qos").expect("dw");
189        let rel = dw.child("reliability").expect("rel");
190        let kind_str = rel.child("kind").expect("kind").text.as_str();
191        let kind = parse_enum(
192            kind_str,
193            &["BEST_EFFORT_RELIABILITY_QOS", "RELIABLE_RELIABILITY_QOS"],
194        )
195        .expect("enum");
196        assert_eq!(kind, "RELIABLE_RELIABILITY_QOS");
197
198        let mbt = rel.child("max_blocking_time").expect("mbt");
199        let sec = parse_duration_sec(mbt.child("sec").expect("sec").text.as_str()).expect("sec");
200        let nsec =
201            parse_duration_nsec(mbt.child("nanosec").expect("nsec").text.as_str()).expect("nsec");
202        let dur = Duration { sec, nanosec: nsec };
203        assert!(dur.is_infinite(), "max_blocking_time should be INFINITE");
204
205        // Resource-Limits: max_samples should resolve to LENGTH_UNLIMITED.
206        let rl = dw.child("resource_limits").expect("rl");
207        let ms = parse_long(rl.child("max_samples").expect("ms").text.as_str()).expect("long");
208        assert_eq!(ms, LENGTH_UNLIMITED);
209
210        // History.depth — a regular long.
211        let hist = dw.child("history").expect("hist");
212        let depth = parse_long(hist.child("depth").expect("depth").text.as_str()).expect("depth");
213        assert_eq!(depth, 10);
214
215        // Inheritance: Derived -> Base -> (none). Resolve via the
216        // inheritance module.
217        let mut by_name: BTreeMap<String, Option<String>> = BTreeMap::new();
218        for p in lib.children_named("qos_profile") {
219            let n = p.attribute("name").expect("named").to_string();
220            let b = p.attribute("base_name").map(ToString::to_string);
221            by_name.insert(n, b);
222        }
223        let chain = resolve_chain("Derived", |n| {
224            by_name
225                .get(n)
226                .cloned()
227                .ok_or_else(|| XmlError::MissingRequiredElement(n.to_string()))
228        })
229        .expect("chain");
230        assert_eq!(
231            chain,
232            alloc::vec!["Base".to_string(), "Derived".to_string()]
233        );
234    }
235
236    /// Detect a base_name cycle across two profiles.
237    #[test]
238    fn detect_cycle_between_profiles() {
239        let xml = r#"<dds>
240            <qos_library name="L">
241                <qos_profile name="A" base_name="B"/>
242                <qos_profile name="B" base_name="A"/>
243            </qos_library>
244        </dds>"#;
245        let doc = parse_xml_tree(xml).expect("parse");
246        let lib = doc.root.child("qos_library").expect("lib");
247        let mut by_name: BTreeMap<String, Option<String>> = BTreeMap::new();
248        for p in lib.children_named("qos_profile") {
249            let n = p.attribute("name").expect("name").to_string();
250            let b = p.attribute("base_name").map(ToString::to_string);
251            by_name.insert(n, b);
252        }
253        let err = resolve_chain("A", |n| {
254            by_name
255                .get(n)
256                .cloned()
257                .ok_or_else(|| XmlError::MissingRequiredElement(n.to_string()))
258        })
259        .expect_err("cycle");
260        assert!(matches!(err, XmlError::CircularInheritance(_)));
261    }
262}