1use alloc::format;
14use alloc::string::{String, ToString};
15use alloc::vec::Vec;
16
17use crate::application::{ApplicationLibrary, parse_app_library_element};
18use crate::domain::{DomainEntry, DomainLibrary, TopicEntry, parse_domain_library_element};
19use crate::errors::XmlError;
20use crate::inheritance::resolve_chain;
21use crate::parser::parse_xml_tree;
22use crate::participant::{
23 DataReaderEntry, DataWriterEntry, DomainParticipantEntry, DomainParticipantLibrary,
24 PublisherEntry, SubscriberEntry, parse_dp_library_element,
25};
26use crate::qos::{EntityQos, QosLibrary};
27use crate::qos_inheritance::resolve_profile;
28use crate::qos_parser::parse_qos_library_element_public;
29use crate::resolver::parse_library_ref;
30use crate::xtypes_def::{TypeDef, TypeLibrary};
31use crate::xtypes_parser::parse_types_element;
32
33#[derive(Debug, Clone, Default, PartialEq, Eq)]
40pub struct DdsXml {
41 pub qos_libraries: Vec<QosLibrary>,
43 pub domain_libraries: Vec<DomainLibrary>,
45 pub participant_libraries: Vec<DomainParticipantLibrary>,
47 pub application_libraries: Vec<ApplicationLibrary>,
49 pub type_libraries: Vec<TypeLibrary>,
51}
52
53#[derive(Debug, Clone, Default, PartialEq, Eq)]
60pub struct ResolvedParticipant {
61 pub lookup_path: String,
63 pub name: String,
65 pub domain_id: u32,
67 pub domain: DomainEntry,
69 pub inheritance_chain: Vec<String>,
71 pub qos: Option<EntityQos>,
73 pub topics: Vec<ResolvedTopic>,
75 pub publishers: Vec<ResolvedPublisher>,
77 pub subscribers: Vec<ResolvedSubscriber>,
79}
80
81#[derive(Debug, Clone, Default, PartialEq, Eq)]
83pub struct ResolvedTopic {
84 pub name: String,
86 pub type_name: String,
88 pub qos: Option<EntityQos>,
91 pub topic_filter: Option<String>,
93}
94
95#[derive(Debug, Clone, Default, PartialEq, Eq)]
97pub struct ResolvedPublisher {
98 pub name: String,
100 pub qos: Option<EntityQos>,
102 pub data_writers: Vec<ResolvedDataWriter>,
104}
105
106#[derive(Debug, Clone, Default, PartialEq, Eq)]
108pub struct ResolvedSubscriber {
109 pub name: String,
111 pub qos: Option<EntityQos>,
113 pub data_readers: Vec<ResolvedDataReader>,
115}
116
117#[derive(Debug, Clone, Default, PartialEq, Eq)]
119pub struct ResolvedDataWriter {
120 pub name: String,
122 pub topic: ResolvedTopic,
124 pub qos: Option<EntityQos>,
127}
128
129#[derive(Debug, Clone, Default, PartialEq, Eq)]
131pub struct ResolvedDataReader {
132 pub name: String,
134 pub topic: ResolvedTopic,
136 pub qos: Option<EntityQos>,
138}
139
140pub trait ParticipantFactoryAdapter {
151 fn apply(&self, participant: &ResolvedParticipant) -> Result<(), XmlError>;
161}
162
163pub fn apply_to_factory(
170 participant: &ResolvedParticipant,
171 factory: &dyn ParticipantFactoryAdapter,
172) -> Result<(), XmlError> {
173 factory.apply(participant)
174}
175
176pub fn parse_dds_xml(xml: &str) -> Result<DdsXml, XmlError> {
187 let doc = parse_xml_tree(xml)?;
188 if doc.root.name != "dds" {
189 return Err(XmlError::InvalidXml(format!(
190 "expected <dds> root, got <{}>",
191 doc.root.name
192 )));
193 }
194 let mut out = DdsXml::default();
195 for child in &doc.root.children {
196 match child.name.as_str() {
197 "qos_library" => out
198 .qos_libraries
199 .push(parse_qos_library_element_public(child)?),
200 "domain_library" => out
201 .domain_libraries
202 .push(parse_domain_library_element(child)?),
203 "domain_participant_library" => out
204 .participant_libraries
205 .push(parse_dp_library_element(child)?),
206 "application_library" => out
207 .application_libraries
208 .push(parse_app_library_element(child)?),
209 "types" => out.type_libraries.push(parse_types_element(child)?),
210 _ => {}
211 }
212 }
213 Ok(out)
214}
215
216impl DdsXml {
217 pub fn find_participant(&self, path: &str) -> Result<&DomainParticipantEntry, XmlError> {
223 let r = parse_library_ref(path)?;
224 if !r.is_qualified() {
225 return Err(XmlError::UnresolvedReference(format!(
226 "participant ref `{path}` must be qualified `library::name`"
227 )));
228 }
229 let lib = self
230 .participant_libraries
231 .iter()
232 .find(|l| l.name == r.library)
233 .ok_or_else(|| {
234 XmlError::UnresolvedReference(format!("participant_library `{}`", r.library))
235 })?;
236 lib.participant(&r.name)
237 .ok_or_else(|| XmlError::UnresolvedReference(format!("participant `{path}`")))
238 }
239
240 pub fn find_domain(&self, path: &str) -> Result<&DomainEntry, XmlError> {
245 let r = parse_library_ref(path)?;
246 if !r.is_qualified() {
247 return Err(XmlError::UnresolvedReference(format!(
248 "domain ref `{path}` must be qualified `library::name`"
249 )));
250 }
251 let lib = self
252 .domain_libraries
253 .iter()
254 .find(|l| l.name == r.library)
255 .ok_or_else(|| {
256 XmlError::UnresolvedReference(format!("domain_library `{}`", r.library))
257 })?;
258 lib.domain(&r.name)
259 .ok_or_else(|| XmlError::UnresolvedReference(format!("domain `{path}`")))
260 }
261
262 pub fn resolve_participant(&self, path: &str) -> Result<ResolvedParticipant, XmlError> {
270 let r = parse_library_ref(path)?;
271 if !r.is_qualified() {
272 return Err(XmlError::UnresolvedReference(format!(
273 "participant ref `{path}` must be qualified `library::name`"
274 )));
275 }
276 let canonical = format!("{}::{}", r.library, r.name);
277
278 let chain = resolve_chain(&canonical, |key| {
280 let kr = parse_library_ref(key)?;
281 let lib = self
282 .participant_libraries
283 .iter()
284 .find(|l| l.name == kr.library)
285 .ok_or_else(|| {
286 XmlError::UnresolvedReference(format!("participant_library `{}`", kr.library))
287 })?;
288 let p = lib
289 .participant(&kr.name)
290 .ok_or_else(|| XmlError::UnresolvedReference(format!("participant `{key}`")))?;
291 Ok(p.base_name.as_deref().map(|b| {
292 if b.contains("::") {
293 b.to_string()
294 } else {
295 format!("{}::{}", kr.library, b)
296 }
297 }))
298 })?;
299
300 let mut domain_ref: Option<String> = None;
302 let mut qos: Option<EntityQos> = None;
303 let mut publishers: Vec<PublisherEntry> = Vec::new();
304 let mut subscribers: Vec<SubscriberEntry> = Vec::new();
305 let mut register_types_ref: Vec<String> = Vec::new();
306 let mut topics_ref: Vec<String> = Vec::new();
307 let mut effective_name = r.name.clone();
308 for key in &chain {
309 let kr = parse_library_ref(key)?;
310 let p = self.find_participant(key)?;
311 domain_ref = Some(p.domain_ref.clone());
312 qos = match (qos, p.qos.as_ref()) {
313 (None, None) => None,
314 (Some(a), None) => Some(a),
315 (None, Some(c)) => Some(c.clone()),
316 (Some(a), Some(c)) => Some(a.merge(c)),
317 };
318 merge_entries(&mut publishers, &p.publishers, |x| x.name.clone());
321 merge_entries(&mut subscribers, &p.subscribers, |x| x.name.clone());
322 merge_str_vec(&mut register_types_ref, &p.register_types_ref);
323 merge_str_vec(&mut topics_ref, &p.topics_ref);
324 effective_name = kr.name;
325 }
326
327 let dref = domain_ref.ok_or_else(|| {
328 XmlError::UnresolvedReference(format!("participant `{canonical}` missing domain_ref"))
329 })?;
330 let domain = self.find_domain(&dref)?.clone();
331
332 let topics: Vec<ResolvedTopic> = if topics_ref.is_empty() {
339 domain
340 .topics
341 .iter()
342 .map(|t| self.resolve_topic_entry(t, &domain))
343 .collect::<Result<Vec<_>, _>>()?
344 } else {
345 let mut out = Vec::new();
346 for tref in &topics_ref {
347 let topic = domain
348 .topic(tref)
349 .ok_or_else(|| XmlError::UnresolvedReference(format!("topic `{tref}`")))?;
350 out.push(self.resolve_topic_entry(topic, &domain)?);
351 }
352 out
353 };
354
355 for rt in ®ister_types_ref {
357 if domain.register_type(rt).is_none() {
358 return Err(XmlError::UnresolvedReference(format!(
359 "register_type `{rt}` in domain `{dref}`"
360 )));
361 }
362 }
363
364 let resolved_pubs = publishers
366 .iter()
367 .map(|pub_e| self.resolve_publisher(pub_e, &domain))
368 .collect::<Result<Vec<_>, _>>()?;
369 let resolved_subs = subscribers
370 .iter()
371 .map(|sub_e| self.resolve_subscriber(sub_e, &domain))
372 .collect::<Result<Vec<_>, _>>()?;
373
374 Ok(ResolvedParticipant {
375 lookup_path: canonical,
376 name: effective_name,
377 domain_id: domain.domain_id,
378 domain,
379 inheritance_chain: chain,
380 qos,
381 topics,
382 publishers: resolved_pubs,
383 subscribers: resolved_subs,
384 })
385 }
386
387 fn resolve_topic_entry(
388 &self,
389 topic: &TopicEntry,
390 domain: &DomainEntry,
391 ) -> Result<ResolvedTopic, XmlError> {
392 let rt = domain
394 .register_type(&topic.register_type_ref)
395 .ok_or_else(|| {
396 XmlError::UnresolvedReference(format!(
397 "register_type `{}`",
398 topic.register_type_ref
399 ))
400 })?;
401 let qos: Option<EntityQos> = if let Some(q) = &topic.topic_qos {
403 Some(q.clone())
404 } else if let Some(profile_ref) = &topic.qos_profile_ref {
405 let r = resolve_profile(&self.qos_libraries, profile_ref)?;
406 r.topic_qos
407 } else {
408 None
409 };
410 Ok(ResolvedTopic {
411 name: topic.name.clone(),
412 type_name: rt.type_ref.clone(),
413 qos,
414 topic_filter: topic.topic_filter.clone(),
415 })
416 }
417
418 fn resolve_publisher(
419 &self,
420 pub_e: &PublisherEntry,
421 domain: &DomainEntry,
422 ) -> Result<ResolvedPublisher, XmlError> {
423 let writers = pub_e
424 .data_writers
425 .iter()
426 .map(|dw| self.resolve_writer(dw, pub_e, domain))
427 .collect::<Result<Vec<_>, _>>()?;
428 Ok(ResolvedPublisher {
429 name: pub_e.name.clone(),
430 qos: pub_e.qos.clone(),
431 data_writers: writers,
432 })
433 }
434
435 fn resolve_subscriber(
436 &self,
437 sub_e: &SubscriberEntry,
438 domain: &DomainEntry,
439 ) -> Result<ResolvedSubscriber, XmlError> {
440 let readers = sub_e
441 .data_readers
442 .iter()
443 .map(|dr| self.resolve_reader(dr, sub_e, domain))
444 .collect::<Result<Vec<_>, _>>()?;
445 Ok(ResolvedSubscriber {
446 name: sub_e.name.clone(),
447 qos: sub_e.qos.clone(),
448 data_readers: readers,
449 })
450 }
451
452 fn resolve_writer(
453 &self,
454 dw: &DataWriterEntry,
455 publisher: &PublisherEntry,
456 domain: &DomainEntry,
457 ) -> Result<ResolvedDataWriter, XmlError> {
458 let topic = domain
459 .topic(&dw.topic_ref)
460 .ok_or_else(|| XmlError::UnresolvedReference(format!("topic `{}`", dw.topic_ref)))?;
461 let resolved_topic = self.resolve_topic_entry(topic, domain)?;
462 let qos: Option<EntityQos> = if let Some(q) = &dw.qos {
464 Some(q.clone())
465 } else if let Some(profile_ref) = &dw.qos_profile_ref {
466 let r = resolve_profile(&self.qos_libraries, profile_ref)?;
467 r.datawriter_qos
468 } else {
469 publisher.qos.clone()
470 };
471 Ok(ResolvedDataWriter {
472 name: dw.name.clone(),
473 topic: resolved_topic,
474 qos,
475 })
476 }
477
478 fn resolve_reader(
479 &self,
480 dr: &DataReaderEntry,
481 subscriber: &SubscriberEntry,
482 domain: &DomainEntry,
483 ) -> Result<ResolvedDataReader, XmlError> {
484 let topic = domain
485 .topic(&dr.topic_ref)
486 .ok_or_else(|| XmlError::UnresolvedReference(format!("topic `{}`", dr.topic_ref)))?;
487 let resolved_topic = self.resolve_topic_entry(topic, domain)?;
488 let qos: Option<EntityQos> = if let Some(q) = &dr.qos {
489 Some(q.clone())
490 } else if let Some(profile_ref) = &dr.qos_profile_ref {
491 let r = resolve_profile(&self.qos_libraries, profile_ref)?;
492 r.datareader_qos
493 } else {
494 subscriber.qos.clone()
495 };
496 Ok(ResolvedDataReader {
497 name: dr.name.clone(),
498 topic: resolved_topic,
499 qos,
500 })
501 }
502
503 #[must_use]
509 pub fn resolve_type(&self, name: &str) -> Option<&TypeDef> {
510 let parts: Vec<&str> = name.split("::").collect();
511 for lib in &self.type_libraries {
512 if let Some(td) = walk_types(&lib.types, &parts) {
513 return Some(td);
514 }
515 }
516 None
517 }
518
519 pub fn resolve_application(&self, path: &str) -> Result<Vec<ResolvedParticipant>, XmlError> {
525 let r = parse_library_ref(path)?;
526 if !r.is_qualified() {
527 return Err(XmlError::UnresolvedReference(format!(
528 "application ref `{path}` must be qualified `library::name`"
529 )));
530 }
531 let lib = self
532 .application_libraries
533 .iter()
534 .find(|l| l.name == r.library)
535 .ok_or_else(|| {
536 XmlError::UnresolvedReference(format!("application_library `{}`", r.library))
537 })?;
538 let app = lib
539 .application(&r.name)
540 .ok_or_else(|| XmlError::UnresolvedReference(format!("application `{path}`")))?;
541 app.domain_participants
542 .iter()
543 .map(|dp| self.resolve_participant(dp))
544 .collect()
545 }
546}
547
548fn merge_entries<T, K, F>(acc: &mut Vec<T>, override_: &[T], key: F)
553where
554 T: Clone,
555 K: Eq,
556 F: Fn(&T) -> K,
557{
558 for item in override_ {
559 let k = key(item);
560 if let Some(pos) = acc.iter().position(|x| key(x) == k) {
561 acc[pos] = item.clone();
562 } else {
563 acc.push(item.clone());
564 }
565 }
566}
567
568fn walk_types<'a>(types: &'a [TypeDef], parts: &[&str]) -> Option<&'a TypeDef> {
572 if parts.is_empty() {
573 return None;
574 }
575 let head = parts[0];
576 for t in types {
577 if t.name() == head {
578 if parts.len() == 1 {
579 return Some(t);
580 }
581 if let TypeDef::Module(m) = t {
582 if let Some(found) = walk_types(&m.types, &parts[1..]) {
583 return Some(found);
584 }
585 }
586 }
587 }
588 if parts.len() == 1 {
589 for t in types {
590 if let TypeDef::Module(m) = t {
591 if let Some(found) = walk_types(&m.types, parts) {
592 return Some(found);
593 }
594 }
595 }
596 }
597 None
598}
599
600fn merge_str_vec(acc: &mut Vec<String>, override_: &[String]) {
601 for s in override_ {
602 if !acc.contains(s) {
603 acc.push(s.clone());
604 }
605 }
606}
607
608#[cfg(test)]
609#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
610mod tests {
611 use super::*;
612
613 #[test]
614 fn parse_empty_dds() {
615 let xml = r#"<dds/>"#;
616 let d = parse_dds_xml(xml).expect("parse");
617 assert!(d.qos_libraries.is_empty());
618 assert!(d.domain_libraries.is_empty());
619 assert!(d.participant_libraries.is_empty());
620 assert!(d.application_libraries.is_empty());
621 }
622
623 #[test]
624 fn parse_mixed_top_level() {
625 let xml = r#"<dds>
626 <qos_library name="ql"><qos_profile name="P"/></qos_library>
627 <domain_library name="dl">
628 <domain name="D" domain_id="0"/>
629 </domain_library>
630 <domain_participant_library name="dpl">
631 <domain_participant name="P" domain_ref="dl::D"/>
632 </domain_participant_library>
633 <application_library name="al">
634 <application name="A">
635 <domain_participant ref="dpl::P"/>
636 </application>
637 </application_library>
638 </dds>"#;
639 let d = parse_dds_xml(xml).expect("parse");
640 assert_eq!(d.qos_libraries.len(), 1);
641 assert_eq!(d.domain_libraries.len(), 1);
642 assert_eq!(d.participant_libraries.len(), 1);
643 assert_eq!(d.application_libraries.len(), 1);
644 }
645
646 #[test]
647 fn non_dds_root_rejected() {
648 let xml = r#"<other/>"#;
649 let err = parse_dds_xml(xml).expect_err("non-dds");
650 assert!(matches!(err, XmlError::InvalidXml(_)));
651 }
652}