zerodds_xml/
application.rs1use 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#[derive(Debug, Clone, Default, PartialEq, Eq)]
27pub struct ApplicationLibrary {
28 pub name: String,
30 pub applications: Vec<ApplicationEntry>,
32}
33
34impl ApplicationLibrary {
35 #[must_use]
37 pub fn application(&self, name: &str) -> Option<&ApplicationEntry> {
38 self.applications.iter().find(|a| a.name == name)
39 }
40}
41
42#[derive(Debug, Clone, Default, PartialEq, Eq)]
44pub struct ApplicationEntry {
45 pub name: String,
47 pub domain_participants: Vec<String>,
49}
50
51pub 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}