Skip to main content

sup_xml_xslt/
error.rs

1//! `XsltError` — single error type covering compile + apply + serialise.
2//!
3//! Wraps the underlying `XmlError` from `sup-xml-core` for any
4//! XPath evaluation failures hit during a transformation (an
5//! `<xsl:value-of select="bad-xpath()"/>` surfaces as `Xpath`),
6//! and exposes engine-specific variants for stylesheet structure
7//! problems and runtime termination.
8
9use sup_xml_core::error::XmlError;
10
11#[derive(Debug)]
12pub enum XsltError {
13    /// Stylesheet structure is malformed — e.g. an `xsl:template`
14    /// with neither `match=` nor `name=`, a top-level element in
15    /// the XSLT namespace that isn't a declaration.
16    InvalidStylesheet(String),
17
18    /// A template body referenced something the static analysis
19    /// couldn't resolve — e.g. `xsl:call-template name="X"` where
20    /// no template `X` exists.
21    UnresolvedReference(String),
22
23    /// An XPath expression failed during transformation.
24    Xpath(XmlError),
25
26    /// `<xsl:message terminate="yes">` fired.  Carries the
27    /// message-element's stringified content.
28    Terminated(String),
29}
30
31impl std::fmt::Display for XsltError {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            XsltError::InvalidStylesheet(msg)  => write!(f, "invalid stylesheet: {msg}"),
35            XsltError::UnresolvedReference(msg) => write!(f, "unresolved reference: {msg}"),
36            XsltError::Xpath(e)                => write!(f, "xpath error: {}", e.message),
37            XsltError::Terminated(msg)         => write!(f, "xsl:message terminate=yes: {msg}"),
38        }
39    }
40}
41
42impl std::error::Error for XsltError {}
43
44impl From<XmlError> for XsltError {
45    fn from(e: XmlError) -> Self { XsltError::Xpath(e) }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use sup_xml_core::error::{ErrorDomain, ErrorLevel};
52
53    #[test]
54    fn display_invalid_stylesheet() {
55        let e = XsltError::InvalidStylesheet("bad <foo>".into());
56        assert_eq!(format!("{e}"), "invalid stylesheet: bad <foo>");
57    }
58
59    #[test]
60    fn display_unresolved_reference() {
61        let e = XsltError::UnresolvedReference("template X".into());
62        assert_eq!(format!("{e}"), "unresolved reference: template X");
63    }
64
65    #[test]
66    fn display_xpath_wraps_inner_message() {
67        let inner = XmlError::new(ErrorDomain::XPath, ErrorLevel::Error, "syntax");
68        let e = XsltError::Xpath(inner);
69        assert_eq!(format!("{e}"), "xpath error: syntax");
70    }
71
72    #[test]
73    fn display_terminated() {
74        let e = XsltError::Terminated("user-requested halt".into());
75        assert_eq!(format!("{e}"), "xsl:message terminate=yes: user-requested halt");
76    }
77
78    #[test]
79    fn from_xml_error_wraps_as_xpath() {
80        let inner = XmlError::new(ErrorDomain::XPath, ErrorLevel::Error, "div by zero");
81        let e: XsltError = inner.into();
82        assert!(matches!(e, XsltError::Xpath(_)));
83        assert_eq!(format!("{e}"), "xpath error: div by zero");
84    }
85
86    #[test]
87    fn implements_std_error_trait() {
88        // Smoke test: XsltError must be usable as `&dyn std::error::Error`.
89        let e = XsltError::InvalidStylesheet("x".into());
90        let err: &dyn std::error::Error = &e;
91        assert!(err.to_string().contains("invalid stylesheet"));
92    }
93
94    #[test]
95    fn debug_format_works() {
96        // The #[derive(Debug)] impl — exercise it so it isn't listed as
97        // an uncovered function.
98        let e = XsltError::Terminated("x".into());
99        let s = format!("{e:?}");
100        assert!(s.contains("Terminated"));
101    }
102}