Skip to main content

zerodds_xml/
errors.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! `XmlError` — error enum for the DDS-XML loader.
4//!
5//! Spec references see the doc comment per variant.
6
7use alloc::string::String;
8use core::fmt;
9
10/// Error while parsing or resolving a DDS-XML document.
11///
12/// Spec source: OMG DDS-XML 1.0 §7.1 (XML Representation Syntax) and
13/// §7.2 (XML Representation of DDS IDL PSM).
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum XmlError {
16    /// XML is not well-formed per [XML] §2.1.
17    ///
18    /// Spec ref: DDS-XML 1.0 §7.1.1 ("XML shall be well-formed").
19    InvalidXml(String),
20
21    /// An element mandatory per spec table 7.2 / 7.3.x is missing.
22    ///
23    /// Spec ref: DDS-XML 1.0 §7.1.4 Tab.7.1, §7.2.x.
24    MissingRequiredElement(String),
25
26    /// An element not present in the element table 7.1/7.2/7.3
27    /// was found. Strict mode rejects; lax mode ignores.
28    ///
29    /// Spec ref: DDS-XML 1.0 §7.1.4 (element value table).
30    UnknownElement(String),
31
32    /// Enum string does not fit the DCPS-IDL whitelist
33    /// (Spec §7.1.4 Tab.7.1 — `enum` values are string literals, *not*
34    /// numeric).
35    ///
36    /// Spec ref: DDS-XML 1.0 §7.1.4 Tab.7.1 (enum), §7.2.1.
37    BadEnum(String),
38
39    /// `base_name` inheritance forms a cycle (A inherits from B inherits from A).
40    ///
41    /// Spec ref: DDS-XML 1.0 §7.3.2.4.2 (QoS Profile Inheritance —
42    /// "shall only inherit from previously defined profiles"). Naive
43    /// implementations can create cycles across library boundaries;
44    /// the loader therefore performs DAG checking.
45    CircularInheritance(String),
46
47    /// Element value outside the spec value range (e.g. `long` >
48    /// `0x7fffffff` without symbol aliasing).
49    ///
50    /// Spec ref: DDS-XML 1.0 §7.1.4 Tab.7.1 (value ranges), §7.2.2
51    /// (`LENGTH_UNLIMITED`, `DURATION_INFINITE_*`).
52    ValueOutOfRange(String),
53
54    /// DoS cap hit — list/string exceeds the upper bound
55    /// configured in the loader (default: 1024 list elements, 64 KiB
56    /// strings).
57    ///
58    /// No direct spec reference; follows the ZeroDDS security posture
59    /// (`docs/spec-coverage/zerodds-xml-1.0.open.md` risks section).
60    LimitExceeded(String),
61
62    /// A cross-reference (e.g. `base_name` of a QoS profile
63    /// inheritance) could not be resolved because the referenced
64    /// item does not exist.
65    ///
66    /// Spec ref: DDS-XML 1.0 §7.3.2.4.2 (QoS profile inheritance).
67    UnresolvedReference(String),
68}
69
70impl fmt::Display for XmlError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match self {
73            Self::InvalidXml(msg) => write!(f, "invalid XML: {msg}"),
74            Self::MissingRequiredElement(name) => {
75                write!(f, "missing required element <{name}>")
76            }
77            Self::UnknownElement(name) => write!(f, "unknown element <{name}>"),
78            Self::BadEnum(value) => write!(f, "invalid enum value `{value}`"),
79            Self::CircularInheritance(chain) => {
80                write!(f, "circular base_name inheritance: {chain}")
81            }
82            Self::ValueOutOfRange(msg) => write!(f, "value out of range: {msg}"),
83            Self::LimitExceeded(msg) => write!(f, "DoS limit exceeded: {msg}"),
84            Self::UnresolvedReference(name) => write!(f, "unresolved reference `{name}`"),
85        }
86    }
87}
88
89#[cfg(feature = "std")]
90impl std::error::Error for XmlError {}
91
92#[cfg(test)]
93#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
94mod tests {
95    use super::*;
96    use alloc::string::ToString;
97
98    #[test]
99    fn display_invalid_xml() {
100        let e = XmlError::InvalidXml("unexpected token".into());
101        assert_eq!(e.to_string(), "invalid XML: unexpected token");
102    }
103
104    #[test]
105    fn display_missing_required() {
106        let e = XmlError::MissingRequiredElement("qos_profile".into());
107        assert_eq!(e.to_string(), "missing required element <qos_profile>");
108    }
109
110    #[test]
111    fn display_unknown_element() {
112        let e = XmlError::UnknownElement("foo".into());
113        assert_eq!(e.to_string(), "unknown element <foo>");
114    }
115
116    #[test]
117    fn display_bad_enum() {
118        let e = XmlError::BadEnum("WRONG".into());
119        assert_eq!(e.to_string(), "invalid enum value `WRONG`");
120    }
121
122    #[test]
123    fn display_circular() {
124        let e = XmlError::CircularInheritance("A -> B -> A".into());
125        assert_eq!(e.to_string(), "circular base_name inheritance: A -> B -> A");
126    }
127
128    #[test]
129    fn display_value_out_of_range() {
130        let e = XmlError::ValueOutOfRange("long > 0x7fffffff".into());
131        assert_eq!(e.to_string(), "value out of range: long > 0x7fffffff");
132    }
133
134    #[test]
135    fn display_limit_exceeded() {
136        let e = XmlError::LimitExceeded("seq > 1024".into());
137        assert_eq!(e.to_string(), "DoS limit exceeded: seq > 1024");
138    }
139
140    #[test]
141    fn equality_and_clone() {
142        let a = XmlError::InvalidXml("x".into());
143        let b = a.clone();
144        assert_eq!(a, b);
145    }
146
147    #[test]
148    fn display_unresolved_reference() {
149        let e = XmlError::UnresolvedReference("MissingProfile".into());
150        assert_eq!(e.to_string(), "unresolved reference `MissingProfile`");
151    }
152}