Skip to main content

zerodds_xml/
application.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! DDS-XML 1.0 §7.3.6 Building Block "Application Library".
4//!
5//! Eine Application bindet einen oder mehrere Domain-Participants
6//! (`<domain_participant ref="…"/>`) zu einer logischen Deployment-Einheit.
7//! Im Spec-Beispiel referenziert eine Application typischerweise einen
8//! einzelnen Participant; mehrere sind erlaubt (Spec §7.3.6.4.2).
9//!
10//! # XML → Rust-Type Mapping
11//!
12//! ```text
13//! <application_library name=…>     | ApplicationLibrary
14//! <application name=…>             | ApplicationEntry
15//! <domain_participant ref=…/>      | ApplicationEntry.domain_participants[i]
16//! ```
17
18use alloc::format;
19use alloc::string::{String, ToString};
20use alloc::vec::Vec;
21
22use crate::errors::XmlError;
23use crate::parser::{XmlElement, parse_xml_tree};
24
25/// Container fuer 1+ Application-Definitionen (§7.3.6.4.1).
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
27pub struct ApplicationLibrary {
28    /// Library-Name.
29    pub name: String,
30    /// Application-Definitionen.
31    pub applications: Vec<ApplicationEntry>,
32}
33
34impl ApplicationLibrary {
35    /// Lookup einer Application anhand ihres Namens.
36    #[must_use]
37    pub fn application(&self, name: &str) -> Option<&ApplicationEntry> {
38        self.applications.iter().find(|a| a.name == name)
39    }
40}
41
42/// Einzelner `<application>`-Eintrag (§7.3.6.4.2).
43#[derive(Debug, Clone, Default, PartialEq, Eq)]
44pub struct ApplicationEntry {
45    /// Application-Name.
46    pub name: String,
47    /// Verweise auf Domain-Participants (`library::participant`).
48    pub domain_participants: Vec<String>,
49}
50
51/// Parsed alle `<application_library>`-Eintraege aus einem `<dds>`-Wurzel-
52/// Element.
53///
54/// # Errors
55/// Wie [`crate::parse_xml_tree`] plus Spec-Validierung.
56pub fn parse_application_libraries(xml: &str) -> Result<Vec<ApplicationLibrary>, XmlError> {
57    let doc = parse_xml_tree(xml)?;
58    if doc.root.name != "dds" {
59        return Err(XmlError::InvalidXml(format!(
60            "expected <dds> root, got <{}>",
61            doc.root.name
62        )));
63    }
64    let mut libs = Vec::new();
65    for lib_node in doc.root.children_named("application_library") {
66        libs.push(parse_app_library_element(lib_node)?);
67    }
68    Ok(libs)
69}
70
71pub(crate) fn parse_app_library_element(el: &XmlElement) -> Result<ApplicationLibrary, XmlError> {
72    let name = el
73        .attribute("name")
74        .ok_or_else(|| XmlError::MissingRequiredElement("application_library@name".into()))?
75        .to_string();
76    let mut applications = Vec::new();
77    for app_node in el.children_named("application") {
78        applications.push(parse_app_element(app_node)?);
79    }
80    Ok(ApplicationLibrary { name, applications })
81}
82
83fn parse_app_element(el: &XmlElement) -> Result<ApplicationEntry, XmlError> {
84    let name = el
85        .attribute("name")
86        .ok_or_else(|| XmlError::MissingRequiredElement("application@name".into()))?
87        .to_string();
88    let mut dps = Vec::new();
89    for child in el.children_named("domain_participant") {
90        let r = child
91            .attribute("ref")
92            .ok_or_else(|| {
93                XmlError::MissingRequiredElement("application/domain_participant@ref".into())
94            })?
95            .to_string();
96        dps.push(r);
97    }
98    Ok(ApplicationEntry {
99        name,
100        domain_participants: dps,
101    })
102}
103
104#[cfg(test)]
105#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn parse_minimal_application() {
111        let xml = r#"<dds>
112          <application_library name="al">
113            <application name="App">
114              <domain_participant ref="dpl::P"/>
115            </application>
116          </application_library>
117        </dds>"#;
118        let libs = parse_application_libraries(xml).expect("parse");
119        assert_eq!(libs[0].name, "al");
120        assert_eq!(libs[0].applications[0].name, "App");
121        assert_eq!(libs[0].applications[0].domain_participants[0], "dpl::P");
122    }
123
124    #[test]
125    fn missing_app_name_rejected() {
126        let xml = r#"<dds>
127          <application_library name="al">
128            <application/>
129          </application_library>
130        </dds>"#;
131        let err = parse_application_libraries(xml).expect_err("missing");
132        assert!(matches!(err, XmlError::MissingRequiredElement(_)));
133    }
134
135    #[test]
136    fn missing_dp_ref_rejected() {
137        let xml = r#"<dds>
138          <application_library name="al">
139            <application name="A">
140              <domain_participant/>
141            </application>
142          </application_library>
143        </dds>"#;
144        let err = parse_application_libraries(xml).expect_err("missing");
145        assert!(matches!(err, XmlError::MissingRequiredElement(_)));
146    }
147}