1use alloc::string::{String, ToString};
23use alloc::vec::Vec;
24
25use zerodds_qos::{ReaderQos, WriterQos};
26
27use crate::errors::XmlError;
28use crate::inheritance::resolve_chain;
29use crate::qos::{EntityQos, QosLibrary, QosProfile};
30use crate::qos_parser::parse_qos_libraries;
31use crate::resolver::parse_library_ref;
32
33#[derive(Debug, Clone, Default)]
35pub struct QosProfileRegistry {
36 libraries: Vec<QosLibrary>,
37}
38
39impl QosProfileRegistry {
40 pub fn from_xml(xml: &str) -> Result<Self, XmlError> {
45 Ok(Self {
46 libraries: parse_qos_libraries(xml)?,
47 })
48 }
49
50 #[cfg(feature = "std")]
55 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, XmlError> {
56 let xml = std::fs::read_to_string(path)
57 .map_err(|e| XmlError::InvalidXml(alloc::format!("cannot read profile file: {e}")))?;
58 Self::from_xml(&xml)
59 }
60
61 #[must_use]
63 pub fn library_count(&self) -> usize {
64 self.libraries.len()
65 }
66
67 pub fn writer_qos(&self, profile_ref: &str) -> Result<WriterQos, XmlError> {
74 Ok(self
75 .resolve(profile_ref, |p| p.datawriter_qos.as_ref())?
76 .into_writer_qos())
77 }
78
79 pub fn reader_qos(&self, profile_ref: &str) -> Result<ReaderQos, XmlError> {
84 Ok(self
85 .resolve(profile_ref, |p| p.datareader_qos.as_ref())?
86 .into_reader_qos())
87 }
88
89 fn resolve<F>(&self, profile_ref: &str, pick: F) -> Result<EntityQos, XmlError>
92 where
93 F: Fn(&QosProfile) -> Option<&EntityQos>,
94 {
95 let r = parse_library_ref(profile_ref)?;
98 let (start_lib, _) = self.find(&r.library, &r.name)?;
99 let start = qualify(start_lib, &r.name);
100
101 let chain = resolve_chain(&start, |qname| {
103 let lr = parse_library_ref(qname)?;
104 let (lib, prof) = self.find(&lr.library, &lr.name)?;
105 Ok(prof.base_name.as_ref().map(|b| {
108 let br = parse_library_ref(b).unwrap_or(crate::resolver::LibraryRef {
109 library: String::new(),
110 name: b.clone(),
111 });
112 if br.is_qualified() {
113 b.clone()
114 } else {
115 qualify(lib, &br.name)
116 }
117 }))
118 })?;
119
120 let mut acc = EntityQos::default();
123 for qname in &chain {
124 let lr = parse_library_ref(qname)?;
125 let (_, prof) = self.find(&lr.library, &lr.name)?;
126 if let Some(eq) = pick(prof) {
127 acc = acc.merge(eq);
128 }
129 }
130 Ok(acc)
131 }
132
133 fn find(&self, lib_name: &str, prof_name: &str) -> Result<(&str, &QosProfile), XmlError> {
136 let lib = if lib_name.is_empty() {
137 self.libraries.first()
138 } else {
139 self.libraries.iter().find(|l| l.name == lib_name)
140 }
141 .ok_or_else(|| XmlError::UnresolvedReference(qualify(lib_name, prof_name)))?;
142 let prof = lib
143 .profile(prof_name)
144 .ok_or_else(|| XmlError::UnresolvedReference(qualify(&lib.name, prof_name)))?;
145 Ok((lib.name.as_str(), prof))
146 }
147}
148
149fn qualify(lib: &str, name: &str) -> String {
151 if lib.is_empty() {
152 name.to_string()
153 } else {
154 let mut s = String::with_capacity(lib.len() + 2 + name.len());
155 s.push_str(lib);
156 s.push_str("::");
157 s.push_str(name);
158 s
159 }
160}
161
162#[cfg(test)]
163#[allow(clippy::expect_used, clippy::unwrap_used)]
164mod tests {
165 use super::*;
166 use zerodds_qos::{HistoryKind, ReliabilityKind};
167
168 const XML: &str = r#"
171<dds>
172 <qos_library name="MyLib">
173 <qos_profile name="Base">
174 <datawriter_qos>
175 <reliability><kind>RELIABLE_RELIABILITY_QOS</kind></reliability>
176 <history><kind>KEEP_LAST_HISTORY_QOS</kind><depth>64</depth></history>
177 </datawriter_qos>
178 <datareader_qos>
179 <reliability><kind>RELIABLE_RELIABILITY_QOS</kind></reliability>
180 </datareader_qos>
181 </qos_profile>
182 <qos_profile name="Derived" base_name="Base">
183 <datawriter_qos>
184 <history><kind>KEEP_LAST_HISTORY_QOS</kind><depth>128</depth></history>
185 </datawriter_qos>
186 </qos_profile>
187 </qos_library>
188</dds>
189"#;
190
191 #[test]
192 fn resolves_base_writer_qos() {
193 let reg = QosProfileRegistry::from_xml(XML).expect("parse");
194 assert_eq!(reg.library_count(), 1);
195 let q = reg.writer_qos("MyLib::Base").expect("base");
196 assert_eq!(q.reliability.kind, ReliabilityKind::Reliable);
197 assert_eq!(q.history.kind, HistoryKind::KeepLast);
198 assert_eq!(q.history.depth, 64);
199 }
200
201 #[test]
202 fn inheritance_derived_overrides_base() {
203 let reg = QosProfileRegistry::from_xml(XML).expect("parse");
204 let q = reg.writer_qos("MyLib::Derived").expect("derived");
205 assert_eq!(q.reliability.kind, ReliabilityKind::Reliable);
207 assert_eq!(q.history.depth, 128);
208 }
209
210 #[test]
211 fn unqualified_ref_uses_first_library() {
212 let reg = QosProfileRegistry::from_xml(XML).expect("parse");
213 let q = reg.writer_qos("Base").expect("unqualified");
214 assert_eq!(q.history.depth, 64);
215 }
216
217 #[test]
218 fn reader_qos_resolves() {
219 let reg = QosProfileRegistry::from_xml(XML).expect("parse");
220 let q = reg.reader_qos("MyLib::Base").expect("reader");
221 assert_eq!(q.reliability.kind, ReliabilityKind::Reliable);
222 }
223
224 #[test]
225 fn missing_profile_is_unresolved_reference() {
226 let reg = QosProfileRegistry::from_xml(XML).expect("parse");
227 match reg.writer_qos("MyLib::Nope") {
228 Err(XmlError::UnresolvedReference(_)) => {}
229 other => panic!("expected UnresolvedReference, got {other:?}"),
230 }
231 }
232}