Skip to main content

zerodds_xml/
resolver.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Cross-Library-Reference-Resolver fuer DDS-XML 1.0 §7.3.4-7.3.6.
4//!
5//! Mehrere Building-Blocks (Domain, DomainParticipant, Application,
6//! QoS-Profile) erlauben qualifizierte Verweise der Form
7//! `library::name`. Dieses Modul stellt die generische Pfad-Zerlegung +
8//! Lookup-Logik bereit, damit die einzelnen Decoder konsistent denselben
9//! Format-Vertrag erfuellen.
10//!
11//! Das DDS-XML 1.0 Spec-Beispiel in Annex C verwendet konsequent das
12//! 2-Segment-Format (`my_lib::MyDomain`), und die OMG-Schema-Definition in
13//! §7.3 erlaubt diese Form als XSD-`token`-Attribut. Single-Segment-Refs
14//! (`MyDomain`) sind in der Spec nicht explizit verboten — wir lassen sie
15//! als Convenience-Form zu, falls das uebergebene Default-Library-Argument
16//! Some(_) ist.
17
18use alloc::format;
19use alloc::string::{String, ToString};
20
21use crate::errors::XmlError;
22
23/// Aufgeloestes 2-Segment-Library-Verweis-Tupel `(library, name)`.
24///
25/// `library = ""` heisst "kein Library-Prefix"; in diesem Fall muss der
26/// Aufrufer einen Default-Library-Scope mitliefern.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct LibraryRef {
29    /// Name der Library (`""` wenn nicht qualifiziert).
30    pub library: String,
31    /// Name des Items innerhalb der Library.
32    pub name: String,
33}
34
35impl LibraryRef {
36    /// `true` wenn der Verweis qualifiziert ist (`library::name`).
37    #[must_use]
38    pub fn is_qualified(&self) -> bool {
39        !self.library.is_empty()
40    }
41}
42
43/// Zerlegt einen Verweis-String in `(library, name)`.
44///
45/// Akzeptierte Formen:
46/// 1. `"library::name"` — qualifiziert.
47/// 2. `"name"` — unqualifiziert (`library` leer).
48///
49/// # Errors
50/// * [`XmlError::UnresolvedReference`] — leerer String, oder `"::name"`,
51///   oder `"library::"`, oder mehr als zwei `::`-Segmente.
52pub fn parse_library_ref(s: &str) -> Result<LibraryRef, XmlError> {
53    let trimmed = s.trim();
54    if trimmed.is_empty() {
55        return Err(XmlError::UnresolvedReference("empty reference".into()));
56    }
57    if let Some((lib, rest)) = trimmed.split_once("::") {
58        if lib.is_empty() {
59            return Err(XmlError::UnresolvedReference(format!(
60                "empty library segment in `{trimmed}`"
61            )));
62        }
63        if rest.contains("::") {
64            return Err(XmlError::UnresolvedReference(format!(
65                "more than two segments in `{trimmed}`"
66            )));
67        }
68        if rest.is_empty() {
69            return Err(XmlError::UnresolvedReference(format!(
70                "empty name segment in `{trimmed}`"
71            )));
72        }
73        Ok(LibraryRef {
74            library: lib.to_string(),
75            name: rest.to_string(),
76        })
77    } else {
78        Ok(LibraryRef {
79            library: String::new(),
80            name: trimmed.to_string(),
81        })
82    }
83}
84
85#[cfg(test)]
86#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn qualified() {
92        let r = parse_library_ref("lib::name").expect("ok");
93        assert_eq!(r.library, "lib");
94        assert_eq!(r.name, "name");
95        assert!(r.is_qualified());
96    }
97
98    #[test]
99    fn unqualified() {
100        let r = parse_library_ref("name").expect("ok");
101        assert_eq!(r.library, "");
102        assert_eq!(r.name, "name");
103        assert!(!r.is_qualified());
104    }
105
106    #[test]
107    fn empty_rejected() {
108        assert!(matches!(
109            parse_library_ref(""),
110            Err(XmlError::UnresolvedReference(_))
111        ));
112        assert!(matches!(
113            parse_library_ref("   "),
114            Err(XmlError::UnresolvedReference(_))
115        ));
116    }
117
118    #[test]
119    fn empty_segment_rejected() {
120        assert!(matches!(
121            parse_library_ref("::name"),
122            Err(XmlError::UnresolvedReference(_))
123        ));
124        assert!(matches!(
125            parse_library_ref("lib::"),
126            Err(XmlError::UnresolvedReference(_))
127        ));
128    }
129
130    #[test]
131    fn three_segments_rejected() {
132        assert!(matches!(
133            parse_library_ref("a::b::c"),
134            Err(XmlError::UnresolvedReference(_))
135        ));
136    }
137}