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