use alloc::string::{String, ToString};
use alloc::vec::Vec;
use zerodds_qos::{ReaderQos, WriterQos};
use crate::errors::XmlError;
use crate::inheritance::resolve_chain;
use crate::qos::{EntityQos, QosLibrary, QosProfile};
use crate::qos_parser::parse_qos_libraries;
use crate::resolver::parse_library_ref;
#[derive(Debug, Clone, Default)]
pub struct QosProfileRegistry {
libraries: Vec<QosLibrary>,
}
impl QosProfileRegistry {
pub fn from_xml(xml: &str) -> Result<Self, XmlError> {
Ok(Self {
libraries: parse_qos_libraries(xml)?,
})
}
#[cfg(feature = "std")]
pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, XmlError> {
let xml = std::fs::read_to_string(path)
.map_err(|e| XmlError::InvalidXml(alloc::format!("cannot read profile file: {e}")))?;
Self::from_xml(&xml)
}
#[must_use]
pub fn library_count(&self) -> usize {
self.libraries.len()
}
pub fn writer_qos(&self, profile_ref: &str) -> Result<WriterQos, XmlError> {
Ok(self
.resolve(profile_ref, |p| p.datawriter_qos.as_ref())?
.into_writer_qos())
}
pub fn reader_qos(&self, profile_ref: &str) -> Result<ReaderQos, XmlError> {
Ok(self
.resolve(profile_ref, |p| p.datareader_qos.as_ref())?
.into_reader_qos())
}
fn resolve<F>(&self, profile_ref: &str, pick: F) -> Result<EntityQos, XmlError>
where
F: Fn(&QosProfile) -> Option<&EntityQos>,
{
let r = parse_library_ref(profile_ref)?;
let (start_lib, _) = self.find(&r.library, &r.name)?;
let start = qualify(start_lib, &r.name);
let chain = resolve_chain(&start, |qname| {
let lr = parse_library_ref(qname)?;
let (lib, prof) = self.find(&lr.library, &lr.name)?;
Ok(prof.base_name.as_ref().map(|b| {
let br = parse_library_ref(b).unwrap_or(crate::resolver::LibraryRef {
library: String::new(),
name: b.clone(),
});
if br.is_qualified() {
b.clone()
} else {
qualify(lib, &br.name)
}
}))
})?;
let mut acc = EntityQos::default();
for qname in &chain {
let lr = parse_library_ref(qname)?;
let (_, prof) = self.find(&lr.library, &lr.name)?;
if let Some(eq) = pick(prof) {
acc = acc.merge(eq);
}
}
Ok(acc)
}
fn find(&self, lib_name: &str, prof_name: &str) -> Result<(&str, &QosProfile), XmlError> {
let lib = if lib_name.is_empty() {
self.libraries.first()
} else {
self.libraries.iter().find(|l| l.name == lib_name)
}
.ok_or_else(|| XmlError::UnresolvedReference(qualify(lib_name, prof_name)))?;
let prof = lib
.profile(prof_name)
.ok_or_else(|| XmlError::UnresolvedReference(qualify(&lib.name, prof_name)))?;
Ok((lib.name.as_str(), prof))
}
}
fn qualify(lib: &str, name: &str) -> String {
if lib.is_empty() {
name.to_string()
} else {
let mut s = String::with_capacity(lib.len() + 2 + name.len());
s.push_str(lib);
s.push_str("::");
s.push_str(name);
s
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
use super::*;
use zerodds_qos::{HistoryKind, ReliabilityKind};
const XML: &str = r#"
<dds>
<qos_library name="MyLib">
<qos_profile name="Base">
<datawriter_qos>
<reliability><kind>RELIABLE_RELIABILITY_QOS</kind></reliability>
<history><kind>KEEP_LAST_HISTORY_QOS</kind><depth>64</depth></history>
</datawriter_qos>
<datareader_qos>
<reliability><kind>RELIABLE_RELIABILITY_QOS</kind></reliability>
</datareader_qos>
</qos_profile>
<qos_profile name="Derived" base_name="Base">
<datawriter_qos>
<history><kind>KEEP_LAST_HISTORY_QOS</kind><depth>128</depth></history>
</datawriter_qos>
</qos_profile>
</qos_library>
</dds>
"#;
#[test]
fn resolves_base_writer_qos() {
let reg = QosProfileRegistry::from_xml(XML).expect("parse");
assert_eq!(reg.library_count(), 1);
let q = reg.writer_qos("MyLib::Base").expect("base");
assert_eq!(q.reliability.kind, ReliabilityKind::Reliable);
assert_eq!(q.history.kind, HistoryKind::KeepLast);
assert_eq!(q.history.depth, 64);
}
#[test]
fn inheritance_derived_overrides_base() {
let reg = QosProfileRegistry::from_xml(XML).expect("parse");
let q = reg.writer_qos("MyLib::Derived").expect("derived");
assert_eq!(q.reliability.kind, ReliabilityKind::Reliable);
assert_eq!(q.history.depth, 128);
}
#[test]
fn unqualified_ref_uses_first_library() {
let reg = QosProfileRegistry::from_xml(XML).expect("parse");
let q = reg.writer_qos("Base").expect("unqualified");
assert_eq!(q.history.depth, 64);
}
#[test]
fn reader_qos_resolves() {
let reg = QosProfileRegistry::from_xml(XML).expect("parse");
let q = reg.reader_qos("MyLib::Base").expect("reader");
assert_eq!(q.reliability.kind, ReliabilityKind::Reliable);
}
#[test]
fn missing_profile_is_unresolved_reference() {
let reg = QosProfileRegistry::from_xml(XML).expect("parse");
match reg.writer_qos("MyLib::Nope") {
Err(XmlError::UnresolvedReference(_)) => {}
other => panic!("expected UnresolvedReference, got {other:?}"),
}
}
}