use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use crate::application::{ApplicationLibrary, parse_app_library_element};
use crate::domain::{DomainEntry, DomainLibrary, TopicEntry, parse_domain_library_element};
use crate::errors::XmlError;
use crate::inheritance::resolve_chain;
use crate::parser::parse_xml_tree;
use crate::participant::{
DataReaderEntry, DataWriterEntry, DomainParticipantEntry, DomainParticipantLibrary,
PublisherEntry, SubscriberEntry, parse_dp_library_element,
};
use crate::qos::{EntityQos, QosLibrary};
use crate::qos_inheritance::resolve_profile;
use crate::qos_parser::parse_qos_library_element_public;
use crate::resolver::parse_library_ref;
use crate::xtypes_def::{TypeDef, TypeLibrary};
use crate::xtypes_parser::parse_types_element;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DdsXml {
pub qos_libraries: Vec<QosLibrary>,
pub domain_libraries: Vec<DomainLibrary>,
pub participant_libraries: Vec<DomainParticipantLibrary>,
pub application_libraries: Vec<ApplicationLibrary>,
pub type_libraries: Vec<TypeLibrary>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ResolvedParticipant {
pub lookup_path: String,
pub name: String,
pub domain_id: u32,
pub domain: DomainEntry,
pub inheritance_chain: Vec<String>,
pub qos: Option<EntityQos>,
pub topics: Vec<ResolvedTopic>,
pub publishers: Vec<ResolvedPublisher>,
pub subscribers: Vec<ResolvedSubscriber>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ResolvedTopic {
pub name: String,
pub type_name: String,
pub qos: Option<EntityQos>,
pub topic_filter: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ResolvedPublisher {
pub name: String,
pub qos: Option<EntityQos>,
pub data_writers: Vec<ResolvedDataWriter>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ResolvedSubscriber {
pub name: String,
pub qos: Option<EntityQos>,
pub data_readers: Vec<ResolvedDataReader>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ResolvedDataWriter {
pub name: String,
pub topic: ResolvedTopic,
pub qos: Option<EntityQos>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ResolvedDataReader {
pub name: String,
pub topic: ResolvedTopic,
pub qos: Option<EntityQos>,
}
pub trait ParticipantFactoryAdapter {
fn apply(&self, participant: &ResolvedParticipant) -> Result<(), XmlError>;
}
pub fn apply_to_factory(
participant: &ResolvedParticipant,
factory: &dyn ParticipantFactoryAdapter,
) -> Result<(), XmlError> {
factory.apply(participant)
}
pub fn parse_dds_xml(xml: &str) -> Result<DdsXml, XmlError> {
let doc = parse_xml_tree(xml)?;
if doc.root.name != "dds" {
return Err(XmlError::InvalidXml(format!(
"expected <dds> root, got <{}>",
doc.root.name
)));
}
let mut out = DdsXml::default();
for child in &doc.root.children {
match child.name.as_str() {
"qos_library" => out
.qos_libraries
.push(parse_qos_library_element_public(child)?),
"domain_library" => out
.domain_libraries
.push(parse_domain_library_element(child)?),
"domain_participant_library" => out
.participant_libraries
.push(parse_dp_library_element(child)?),
"application_library" => out
.application_libraries
.push(parse_app_library_element(child)?),
"types" => out.type_libraries.push(parse_types_element(child)?),
_ => {}
}
}
Ok(out)
}
impl DdsXml {
pub fn find_participant(&self, path: &str) -> Result<&DomainParticipantEntry, XmlError> {
let r = parse_library_ref(path)?;
if !r.is_qualified() {
return Err(XmlError::UnresolvedReference(format!(
"participant ref `{path}` must be qualified `library::name`"
)));
}
let lib = self
.participant_libraries
.iter()
.find(|l| l.name == r.library)
.ok_or_else(|| {
XmlError::UnresolvedReference(format!("participant_library `{}`", r.library))
})?;
lib.participant(&r.name)
.ok_or_else(|| XmlError::UnresolvedReference(format!("participant `{path}`")))
}
pub fn find_domain(&self, path: &str) -> Result<&DomainEntry, XmlError> {
let r = parse_library_ref(path)?;
if !r.is_qualified() {
return Err(XmlError::UnresolvedReference(format!(
"domain ref `{path}` must be qualified `library::name`"
)));
}
let lib = self
.domain_libraries
.iter()
.find(|l| l.name == r.library)
.ok_or_else(|| {
XmlError::UnresolvedReference(format!("domain_library `{}`", r.library))
})?;
lib.domain(&r.name)
.ok_or_else(|| XmlError::UnresolvedReference(format!("domain `{path}`")))
}
pub fn resolve_participant(&self, path: &str) -> Result<ResolvedParticipant, XmlError> {
let r = parse_library_ref(path)?;
if !r.is_qualified() {
return Err(XmlError::UnresolvedReference(format!(
"participant ref `{path}` must be qualified `library::name`"
)));
}
let canonical = format!("{}::{}", r.library, r.name);
let chain = resolve_chain(&canonical, |key| {
let kr = parse_library_ref(key)?;
let lib = self
.participant_libraries
.iter()
.find(|l| l.name == kr.library)
.ok_or_else(|| {
XmlError::UnresolvedReference(format!("participant_library `{}`", kr.library))
})?;
let p = lib
.participant(&kr.name)
.ok_or_else(|| XmlError::UnresolvedReference(format!("participant `{key}`")))?;
Ok(p.base_name.as_deref().map(|b| {
if b.contains("::") {
b.to_string()
} else {
format!("{}::{}", kr.library, b)
}
}))
})?;
let mut domain_ref: Option<String> = None;
let mut qos: Option<EntityQos> = None;
let mut publishers: Vec<PublisherEntry> = Vec::new();
let mut subscribers: Vec<SubscriberEntry> = Vec::new();
let mut register_types_ref: Vec<String> = Vec::new();
let mut topics_ref: Vec<String> = Vec::new();
let mut effective_name = r.name.clone();
for key in &chain {
let kr = parse_library_ref(key)?;
let p = self.find_participant(key)?;
domain_ref = Some(p.domain_ref.clone());
qos = match (qos, p.qos.as_ref()) {
(None, None) => None,
(Some(a), None) => Some(a),
(None, Some(c)) => Some(c.clone()),
(Some(a), Some(c)) => Some(a.merge(c)),
};
merge_entries(&mut publishers, &p.publishers, |x| x.name.clone());
merge_entries(&mut subscribers, &p.subscribers, |x| x.name.clone());
merge_str_vec(&mut register_types_ref, &p.register_types_ref);
merge_str_vec(&mut topics_ref, &p.topics_ref);
effective_name = kr.name;
}
let dref = domain_ref.ok_or_else(|| {
XmlError::UnresolvedReference(format!("participant `{canonical}` missing domain_ref"))
})?;
let domain = self.find_domain(&dref)?.clone();
let topics: Vec<ResolvedTopic> = if topics_ref.is_empty() {
domain
.topics
.iter()
.map(|t| self.resolve_topic_entry(t, &domain))
.collect::<Result<Vec<_>, _>>()?
} else {
let mut out = Vec::new();
for tref in &topics_ref {
let topic = domain
.topic(tref)
.ok_or_else(|| XmlError::UnresolvedReference(format!("topic `{tref}`")))?;
out.push(self.resolve_topic_entry(topic, &domain)?);
}
out
};
for rt in ®ister_types_ref {
if domain.register_type(rt).is_none() {
return Err(XmlError::UnresolvedReference(format!(
"register_type `{rt}` in domain `{dref}`"
)));
}
}
let resolved_pubs = publishers
.iter()
.map(|pub_e| self.resolve_publisher(pub_e, &domain))
.collect::<Result<Vec<_>, _>>()?;
let resolved_subs = subscribers
.iter()
.map(|sub_e| self.resolve_subscriber(sub_e, &domain))
.collect::<Result<Vec<_>, _>>()?;
Ok(ResolvedParticipant {
lookup_path: canonical,
name: effective_name,
domain_id: domain.domain_id,
domain,
inheritance_chain: chain,
qos,
topics,
publishers: resolved_pubs,
subscribers: resolved_subs,
})
}
fn resolve_topic_entry(
&self,
topic: &TopicEntry,
domain: &DomainEntry,
) -> Result<ResolvedTopic, XmlError> {
let rt = domain
.register_type(&topic.register_type_ref)
.ok_or_else(|| {
XmlError::UnresolvedReference(format!(
"register_type `{}`",
topic.register_type_ref
))
})?;
let qos: Option<EntityQos> = if let Some(q) = &topic.topic_qos {
Some(q.clone())
} else if let Some(profile_ref) = &topic.qos_profile_ref {
let r = resolve_profile(&self.qos_libraries, profile_ref)?;
r.topic_qos
} else {
None
};
Ok(ResolvedTopic {
name: topic.name.clone(),
type_name: rt.type_ref.clone(),
qos,
topic_filter: topic.topic_filter.clone(),
})
}
fn resolve_publisher(
&self,
pub_e: &PublisherEntry,
domain: &DomainEntry,
) -> Result<ResolvedPublisher, XmlError> {
let writers = pub_e
.data_writers
.iter()
.map(|dw| self.resolve_writer(dw, pub_e, domain))
.collect::<Result<Vec<_>, _>>()?;
Ok(ResolvedPublisher {
name: pub_e.name.clone(),
qos: pub_e.qos.clone(),
data_writers: writers,
})
}
fn resolve_subscriber(
&self,
sub_e: &SubscriberEntry,
domain: &DomainEntry,
) -> Result<ResolvedSubscriber, XmlError> {
let readers = sub_e
.data_readers
.iter()
.map(|dr| self.resolve_reader(dr, sub_e, domain))
.collect::<Result<Vec<_>, _>>()?;
Ok(ResolvedSubscriber {
name: sub_e.name.clone(),
qos: sub_e.qos.clone(),
data_readers: readers,
})
}
fn resolve_writer(
&self,
dw: &DataWriterEntry,
publisher: &PublisherEntry,
domain: &DomainEntry,
) -> Result<ResolvedDataWriter, XmlError> {
let topic = domain
.topic(&dw.topic_ref)
.ok_or_else(|| XmlError::UnresolvedReference(format!("topic `{}`", dw.topic_ref)))?;
let resolved_topic = self.resolve_topic_entry(topic, domain)?;
let qos: Option<EntityQos> = if let Some(q) = &dw.qos {
Some(q.clone())
} else if let Some(profile_ref) = &dw.qos_profile_ref {
let r = resolve_profile(&self.qos_libraries, profile_ref)?;
r.datawriter_qos
} else {
publisher.qos.clone()
};
Ok(ResolvedDataWriter {
name: dw.name.clone(),
topic: resolved_topic,
qos,
})
}
fn resolve_reader(
&self,
dr: &DataReaderEntry,
subscriber: &SubscriberEntry,
domain: &DomainEntry,
) -> Result<ResolvedDataReader, XmlError> {
let topic = domain
.topic(&dr.topic_ref)
.ok_or_else(|| XmlError::UnresolvedReference(format!("topic `{}`", dr.topic_ref)))?;
let resolved_topic = self.resolve_topic_entry(topic, domain)?;
let qos: Option<EntityQos> = if let Some(q) = &dr.qos {
Some(q.clone())
} else if let Some(profile_ref) = &dr.qos_profile_ref {
let r = resolve_profile(&self.qos_libraries, profile_ref)?;
r.datareader_qos
} else {
subscriber.qos.clone()
};
Ok(ResolvedDataReader {
name: dr.name.clone(),
topic: resolved_topic,
qos,
})
}
#[must_use]
pub fn resolve_type(&self, name: &str) -> Option<&TypeDef> {
let parts: Vec<&str> = name.split("::").collect();
for lib in &self.type_libraries {
if let Some(td) = walk_types(&lib.types, &parts) {
return Some(td);
}
}
None
}
pub fn resolve_application(&self, path: &str) -> Result<Vec<ResolvedParticipant>, XmlError> {
let r = parse_library_ref(path)?;
if !r.is_qualified() {
return Err(XmlError::UnresolvedReference(format!(
"application ref `{path}` must be qualified `library::name`"
)));
}
let lib = self
.application_libraries
.iter()
.find(|l| l.name == r.library)
.ok_or_else(|| {
XmlError::UnresolvedReference(format!("application_library `{}`", r.library))
})?;
let app = lib
.application(&r.name)
.ok_or_else(|| XmlError::UnresolvedReference(format!("application `{path}`")))?;
app.domain_participants
.iter()
.map(|dp| self.resolve_participant(dp))
.collect()
}
}
fn merge_entries<T, K, F>(acc: &mut Vec<T>, override_: &[T], key: F)
where
T: Clone,
K: Eq,
F: Fn(&T) -> K,
{
for item in override_ {
let k = key(item);
if let Some(pos) = acc.iter().position(|x| key(x) == k) {
acc[pos] = item.clone();
} else {
acc.push(item.clone());
}
}
}
fn walk_types<'a>(types: &'a [TypeDef], parts: &[&str]) -> Option<&'a TypeDef> {
if parts.is_empty() {
return None;
}
let head = parts[0];
for t in types {
if t.name() == head {
if parts.len() == 1 {
return Some(t);
}
if let TypeDef::Module(m) = t {
if let Some(found) = walk_types(&m.types, &parts[1..]) {
return Some(found);
}
}
}
}
if parts.len() == 1 {
for t in types {
if let TypeDef::Module(m) = t {
if let Some(found) = walk_types(&m.types, parts) {
return Some(found);
}
}
}
}
None
}
fn merge_str_vec(acc: &mut Vec<String>, override_: &[String]) {
for s in override_ {
if !acc.contains(s) {
acc.push(s.clone());
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn parse_empty_dds() {
let xml = r#"<dds/>"#;
let d = parse_dds_xml(xml).expect("parse");
assert!(d.qos_libraries.is_empty());
assert!(d.domain_libraries.is_empty());
assert!(d.participant_libraries.is_empty());
assert!(d.application_libraries.is_empty());
}
#[test]
fn parse_mixed_top_level() {
let xml = r#"<dds>
<qos_library name="ql"><qos_profile name="P"/></qos_library>
<domain_library name="dl">
<domain name="D" domain_id="0"/>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="dl::D"/>
</domain_participant_library>
<application_library name="al">
<application name="A">
<domain_participant ref="dpl::P"/>
</application>
</application_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
assert_eq!(d.qos_libraries.len(), 1);
assert_eq!(d.domain_libraries.len(), 1);
assert_eq!(d.participant_libraries.len(), 1);
assert_eq!(d.application_libraries.len(), 1);
}
#[test]
fn non_dds_root_rejected() {
let xml = r#"<other/>"#;
let err = parse_dds_xml(xml).expect_err("non-dds");
assert!(matches!(err, XmlError::InvalidXml(_)));
}
}