Skip to main content

zerodds_xml/
participant.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! DDS-XML 1.0 §7.3.5 Building Block "Domain Participant Library".
4//!
5//! Ein `<domain_participant_library>` traegt 1+ `<domain_participant>`-
6//! Eintraege; jeder traegt `domain_ref` (Verweis auf eine Domain in einer
7//! `<domain_library>`), optional `base_name` (Participant-Inheritance),
8//! optional inline `<domain_participant_qos>`, sowie Listen von
9//! Publishers/Subscribers mit eingebetteten DataWriters/DataReaders.
10//!
11//! Spec-Quelle: OMG DDS-XML 1.0 §7.3.5 (Domain Participant Library Building
12//! Block).
13//!
14//! # XML → Rust-Type Mapping
15//!
16//! ```text
17//! <domain_participant_library name=…> | DomainParticipantLibrary
18//! <domain_participant name=… domain_ref=… base_name=…>
19//!                                     | DomainParticipantEntry
20//! <domain_participant_qos>            | EntityQos
21//! <register_type ref=…/>              | DomainParticipantEntry.register_types_ref
22//! <topic ref=…/>                      | DomainParticipantEntry.topics_ref
23//! <publisher name=…>                  | PublisherEntry
24//! <publisher_qos>                     | PublisherEntry.qos
25//! <data_writer name=… topic_ref=…>    | DataWriterEntry
26//! <datawriter_qos>                    | DataWriterEntry.qos
27//! <subscriber name=…>                 | SubscriberEntry
28//! <data_reader name=… topic_ref=…>    | DataReaderEntry
29//! ```
30
31use alloc::format;
32use alloc::string::{String, ToString};
33use alloc::vec::Vec;
34
35use crate::errors::XmlError;
36use crate::parser::{XmlElement, parse_xml_tree};
37use crate::qos::EntityQos;
38use crate::qos_parser::parse_entity_qos_public;
39
40/// Container fuer 1+ Participant-Definitionen (§7.3.5.4.1).
41#[derive(Debug, Clone, Default, PartialEq, Eq)]
42pub struct DomainParticipantLibrary {
43    /// Library-Name.
44    pub name: String,
45    /// Participant-Definitionen in Dokument-Reihenfolge.
46    pub participants: Vec<DomainParticipantEntry>,
47}
48
49impl DomainParticipantLibrary {
50    /// Sucht einen Participant innerhalb dieser Library.
51    #[must_use]
52    pub fn participant(&self, name: &str) -> Option<&DomainParticipantEntry> {
53        self.participants.iter().find(|p| p.name == name)
54    }
55}
56
57/// Einzelner `<domain_participant>` (§7.3.5.4.2).
58#[derive(Debug, Clone, Default, PartialEq, Eq)]
59pub struct DomainParticipantEntry {
60    /// Participant-Name (Attribut `name`).
61    pub name: String,
62    /// Verweis auf eine Domain (`library::name`).
63    pub domain_ref: String,
64    /// Optionaler `base_name`-Verweis fuer Inheritance.
65    pub base_name: Option<String>,
66    /// Inline-`<domain_participant_qos>`.
67    pub qos: Option<EntityQos>,
68    /// Verweise auf `<register_type>`-Eintraege in der Domain
69    /// (`<register_type ref="…"/>`-Children).
70    pub register_types_ref: Vec<String>,
71    /// Verweise auf `<topic>`-Eintraege in der Domain (`<topic ref="…"/>`).
72    pub topics_ref: Vec<String>,
73    /// Eingebettete Publisher.
74    pub publishers: Vec<PublisherEntry>,
75    /// Eingebettete Subscriber.
76    pub subscribers: Vec<SubscriberEntry>,
77}
78
79/// `<publisher>` (§7.3.5.4.4).
80#[derive(Debug, Clone, Default, PartialEq, Eq)]
81pub struct PublisherEntry {
82    /// Publisher-Name.
83    pub name: String,
84    /// Inline-`<publisher_qos>`.
85    pub qos: Option<EntityQos>,
86    /// DataWriter-Liste.
87    pub data_writers: Vec<DataWriterEntry>,
88}
89
90/// `<subscriber>` (§7.3.5.4.5).
91#[derive(Debug, Clone, Default, PartialEq, Eq)]
92pub struct SubscriberEntry {
93    /// Subscriber-Name.
94    pub name: String,
95    /// Inline-`<subscriber_qos>`.
96    pub qos: Option<EntityQos>,
97    /// DataReader-Liste.
98    pub data_readers: Vec<DataReaderEntry>,
99}
100
101/// `<data_writer>` (§7.3.5.4.6).
102#[derive(Debug, Clone, Default, PartialEq, Eq)]
103pub struct DataWriterEntry {
104    /// Writer-Name.
105    pub name: String,
106    /// Verweis auf einen `<topic>`-Eintrag in der Domain.
107    pub topic_ref: String,
108    /// Inline-`<datawriter_qos>`.
109    pub qos: Option<EntityQos>,
110    /// Optionaler `qos_profile_ref`.
111    pub qos_profile_ref: Option<String>,
112}
113
114/// `<data_reader>` (§7.3.5.4.7).
115#[derive(Debug, Clone, Default, PartialEq, Eq)]
116pub struct DataReaderEntry {
117    /// Reader-Name.
118    pub name: String,
119    /// Verweis auf einen `<topic>`-Eintrag in der Domain.
120    pub topic_ref: String,
121    /// Inline-`<datareader_qos>`.
122    pub qos: Option<EntityQos>,
123    /// Optionaler `qos_profile_ref`.
124    pub qos_profile_ref: Option<String>,
125}
126
127/// Parsed alle `<domain_participant_library>`-Eintraege aus einem
128/// `<dds>`-Wurzel-Element.
129///
130/// # Errors
131/// Wie [`crate::parse_xml_tree`] plus Spec-Validierung.
132pub fn parse_domain_participant_libraries(
133    xml: &str,
134) -> Result<Vec<DomainParticipantLibrary>, XmlError> {
135    let doc = parse_xml_tree(xml)?;
136    if doc.root.name != "dds" {
137        return Err(XmlError::InvalidXml(format!(
138            "expected <dds> root, got <{}>",
139            doc.root.name
140        )));
141    }
142    let mut libs = Vec::new();
143    for lib_node in doc.root.children_named("domain_participant_library") {
144        libs.push(parse_dp_library_element(lib_node)?);
145    }
146    Ok(libs)
147}
148
149pub(crate) fn parse_dp_library_element(
150    el: &XmlElement,
151) -> Result<DomainParticipantLibrary, XmlError> {
152    let name = el
153        .attribute("name")
154        .ok_or_else(|| XmlError::MissingRequiredElement("domain_participant_library@name".into()))?
155        .to_string();
156    let mut participants = Vec::new();
157    for p in el.children_named("domain_participant") {
158        participants.push(parse_dp_element(p)?);
159    }
160    Ok(DomainParticipantLibrary { name, participants })
161}
162
163fn parse_dp_element(el: &XmlElement) -> Result<DomainParticipantEntry, XmlError> {
164    let name = el
165        .attribute("name")
166        .ok_or_else(|| XmlError::MissingRequiredElement("domain_participant@name".into()))?
167        .to_string();
168    let domain_ref = el
169        .attribute("domain_ref")
170        .ok_or_else(|| XmlError::MissingRequiredElement("domain_participant@domain_ref".into()))?
171        .to_string();
172    let base_name = el.attribute("base_name").map(ToString::to_string);
173
174    let mut entry = DomainParticipantEntry {
175        name,
176        domain_ref,
177        base_name,
178        ..DomainParticipantEntry::default()
179    };
180
181    for child in &el.children {
182        match child.name.as_str() {
183            "domain_participant_qos" => entry.qos = Some(parse_entity_qos_public(child)?),
184            "register_type" => {
185                let r = child
186                    .attribute("ref")
187                    .or_else(|| child.attribute("name"))
188                    .ok_or_else(|| {
189                        XmlError::MissingRequiredElement(
190                            "domain_participant/register_type@ref".into(),
191                        )
192                    })?
193                    .to_string();
194                entry.register_types_ref.push(r);
195            }
196            "topic" => {
197                let r = child
198                    .attribute("ref")
199                    .or_else(|| child.attribute("name"))
200                    .ok_or_else(|| {
201                        XmlError::MissingRequiredElement("domain_participant/topic@ref".into())
202                    })?
203                    .to_string();
204                entry.topics_ref.push(r);
205            }
206            "publisher" => entry.publishers.push(parse_publisher_element(child)?),
207            "subscriber" => entry.subscribers.push(parse_subscriber_element(child)?),
208            _ => {}
209        }
210    }
211    Ok(entry)
212}
213
214fn parse_publisher_element(el: &XmlElement) -> Result<PublisherEntry, XmlError> {
215    let name = el
216        .attribute("name")
217        .ok_or_else(|| XmlError::MissingRequiredElement("publisher@name".into()))?
218        .to_string();
219    let mut qos: Option<EntityQos> = None;
220    let mut data_writers = Vec::new();
221    for child in &el.children {
222        match child.name.as_str() {
223            "publisher_qos" => qos = Some(parse_entity_qos_public(child)?),
224            "data_writer" => data_writers.push(parse_dw_element(child)?),
225            _ => {}
226        }
227    }
228    Ok(PublisherEntry {
229        name,
230        qos,
231        data_writers,
232    })
233}
234
235fn parse_subscriber_element(el: &XmlElement) -> Result<SubscriberEntry, XmlError> {
236    let name = el
237        .attribute("name")
238        .ok_or_else(|| XmlError::MissingRequiredElement("subscriber@name".into()))?
239        .to_string();
240    let mut qos: Option<EntityQos> = None;
241    let mut data_readers = Vec::new();
242    for child in &el.children {
243        match child.name.as_str() {
244            "subscriber_qos" => qos = Some(parse_entity_qos_public(child)?),
245            "data_reader" => data_readers.push(parse_dr_element(child)?),
246            _ => {}
247        }
248    }
249    Ok(SubscriberEntry {
250        name,
251        qos,
252        data_readers,
253    })
254}
255
256fn parse_dw_element(el: &XmlElement) -> Result<DataWriterEntry, XmlError> {
257    let name = el
258        .attribute("name")
259        .ok_or_else(|| XmlError::MissingRequiredElement("data_writer@name".into()))?
260        .to_string();
261    let topic_ref = el
262        .attribute("topic_ref")
263        .ok_or_else(|| XmlError::MissingRequiredElement("data_writer@topic_ref".into()))?
264        .to_string();
265    let qos_profile_ref = el.attribute("qos_profile_ref").map(ToString::to_string);
266    let mut qos: Option<EntityQos> = None;
267    for child in &el.children {
268        if child.name == "datawriter_qos" {
269            qos = Some(parse_entity_qos_public(child)?);
270        }
271    }
272    Ok(DataWriterEntry {
273        name,
274        topic_ref,
275        qos,
276        qos_profile_ref,
277    })
278}
279
280fn parse_dr_element(el: &XmlElement) -> Result<DataReaderEntry, XmlError> {
281    let name = el
282        .attribute("name")
283        .ok_or_else(|| XmlError::MissingRequiredElement("data_reader@name".into()))?
284        .to_string();
285    let topic_ref = el
286        .attribute("topic_ref")
287        .ok_or_else(|| XmlError::MissingRequiredElement("data_reader@topic_ref".into()))?
288        .to_string();
289    let qos_profile_ref = el.attribute("qos_profile_ref").map(ToString::to_string);
290    let mut qos: Option<EntityQos> = None;
291    for child in &el.children {
292        if child.name == "datareader_qos" {
293            qos = Some(parse_entity_qos_public(child)?);
294        }
295    }
296    Ok(DataReaderEntry {
297        name,
298        topic_ref,
299        qos,
300        qos_profile_ref,
301    })
302}
303
304#[cfg(test)]
305#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
306mod tests {
307    use super::*;
308
309    #[test]
310    fn parse_minimal_participant() {
311        let xml = r#"<dds>
312          <domain_participant_library name="dpl">
313            <domain_participant name="P" domain_ref="my_lib::MyDomain"/>
314          </domain_participant_library>
315        </dds>"#;
316        let libs = parse_domain_participant_libraries(xml).expect("parse");
317        assert_eq!(libs[0].name, "dpl");
318        assert_eq!(libs[0].participants[0].name, "P");
319        assert_eq!(libs[0].participants[0].domain_ref, "my_lib::MyDomain");
320    }
321
322    #[test]
323    fn parse_pub_with_writer() {
324        let xml = r#"<dds>
325          <domain_participant_library name="dpl">
326            <domain_participant name="P" domain_ref="dl::D">
327              <publisher name="Pub1">
328                <data_writer name="W1" topic_ref="StateTopic">
329                  <datawriter_qos>
330                    <reliability><kind>RELIABLE</kind></reliability>
331                  </datawriter_qos>
332                </data_writer>
333              </publisher>
334            </domain_participant>
335          </domain_participant_library>
336        </dds>"#;
337        let libs = parse_domain_participant_libraries(xml).expect("parse");
338        let p = &libs[0].participants[0];
339        let pub1 = &p.publishers[0];
340        assert_eq!(pub1.name, "Pub1");
341        let w = &pub1.data_writers[0];
342        assert_eq!(w.name, "W1");
343        assert_eq!(w.topic_ref, "StateTopic");
344        assert!(w.qos.is_some());
345    }
346
347    #[test]
348    fn missing_domain_ref_rejected() {
349        let xml = r#"<dds>
350          <domain_participant_library name="dpl">
351            <domain_participant name="P"/>
352          </domain_participant_library>
353        </dds>"#;
354        let err = parse_domain_participant_libraries(xml).expect_err("missing");
355        assert!(matches!(err, XmlError::MissingRequiredElement(_)));
356    }
357
358    #[test]
359    fn dw_missing_topic_ref_rejected() {
360        let xml = r#"<dds>
361          <domain_participant_library name="dpl">
362            <domain_participant name="P" domain_ref="dl::D">
363              <publisher name="Pub1">
364                <data_writer name="W1"/>
365              </publisher>
366            </domain_participant>
367          </domain_participant_library>
368        </dds>"#;
369        let err = parse_domain_participant_libraries(xml).expect_err("missing");
370        assert!(matches!(err, XmlError::MissingRequiredElement(_)));
371    }
372}