Skip to main content

ifc_xml/
error.rs

1//! Why an ifcXML operation failed.
2
3use std::fmt;
4use thiserror::Error;
5
6/// Stable, codec-specific location of an ifcXML parse failure.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct XmlPath(String);
9
10impl XmlPath {
11    pub(crate) fn new(path: String) -> Self {
12        Self(path)
13    }
14
15    /// The XPath-like location string.
16    #[must_use]
17    pub fn as_str(&self) -> &str {
18        &self.0
19    }
20}
21
22impl fmt::Display for XmlPath {
23    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24        formatter.write_str(&self.0)
25    }
26}
27
28/// Failures specific to reading or writing ifcXML.
29#[derive(Debug, Error)]
30#[non_exhaustive]
31pub enum XmlError {
32    /// The document is not well-formed XML.
33    #[error("malformed XML: {0}")]
34    Malformed(String),
35    /// An `id` attribute was not in the expected `i<number>` form.
36    #[error("unparseable entity id {0:?}")]
37    BadId(String),
38    /// A typed scalar did not contain a value of its declared kind.
39    #[error("invalid {kind} scalar {value:?}")]
40    InvalidScalar {
41        /// Declared scalar kind.
42        kind: String,
43        /// Invalid lexical value.
44        value: String,
45    },
46    /// A non-empty explicit `kind` is not part of this lossless dialect.
47    #[error("unknown value kind {0:?}")]
48    UnknownKind(String),
49    /// An element resolved outside the selected release namespace.
50    #[error("element `{element}` has namespace {found:?}; expected `{expected}`")]
51    Namespace {
52        /// Element local name.
53        element: String,
54        /// Required namespace URI.
55        expected: &'static str,
56        /// Resolved namespace URI, if any.
57        found: Option<String>,
58    },
59    /// Root schema metadata disagrees with the selected release profile.
60    #[error("ifcXML profile expects schema `{expected}`, found {found:?}")]
61    Profile {
62        /// Required schema token.
63        expected: &'static str,
64        /// Declared root schema token, if any.
65        found: Option<String>,
66    },
67    /// A strict document did not have the required root element.
68    #[error("strict ifcXML requires root `ifcXML`, found {found:?}")]
69    Root {
70        /// First element local name, if any.
71        found: Option<String>,
72    },
73    /// A parsing error with its entity/value location retained.
74    #[error("{path}: {source}")]
75    At {
76        /// XPath-like parser location.
77        path: XmlPath,
78        /// Underlying typed parse error.
79        #[source]
80        source: Box<XmlError>,
81    },
82    /// Writing to the output buffer failed.
83    #[error("write failed: {0}")]
84    Write(String),
85}
86
87impl XmlError {
88    /// Location retained by the reader, when the failure occurred in document content.
89    #[must_use]
90    pub fn path(&self) -> Option<&XmlPath> {
91        match self {
92            Self::At { path, .. } => Some(path),
93            _ => None,
94        }
95    }
96
97    /// Innermost codec error, without discarding the inspectable path.
98    #[must_use]
99    pub fn root_cause(&self) -> &Self {
100        match self {
101            Self::At { source, .. } => source.root_cause(),
102            error => error,
103        }
104    }
105
106    pub(crate) fn at(self, path: String) -> Self {
107        match self {
108            Self::At { .. } => self,
109            source => Self::At {
110                path: XmlPath::new(path),
111                source: Box::new(source),
112            },
113        }
114    }
115}
116
117impl From<quick_xml::Error> for XmlError {
118    fn from(error: quick_xml::Error) -> Self {
119        Self::Malformed(error.to_string())
120    }
121}