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    /// Two attributes or child elements of one entity name the same slot.
50    #[error("`{name}` names slot {slot}, which is already set")]
51    DuplicateSlot {
52        /// The second name resolving to the slot.
53        name: String,
54        /// The zero-based positional slot.
55        slot: usize,
56    },
57    /// A positional name skips slots that no schema declares as omittable.
58    #[error("`{name}` names slot {slot}, but slot {missing} is absent")]
59    MissingSlot {
60        /// The name that skipped ahead.
61        name: String,
62        /// The zero-based slot it names.
63        slot: usize,
64        /// The first slot left without a value.
65        missing: usize,
66    },
67    /// An element resolved outside the selected release namespace.
68    #[error("element `{element}` has namespace {found:?}; expected `{expected}`")]
69    Namespace {
70        /// Element local name.
71        element: String,
72        /// Required namespace URI.
73        expected: &'static str,
74        /// Resolved namespace URI, if any.
75        found: Option<String>,
76    },
77    /// Root schema metadata disagrees with the selected release profile.
78    #[error("ifcXML profile expects schema `{expected}`, found {found:?}")]
79    Profile {
80        /// Required schema token.
81        expected: &'static str,
82        /// Declared root schema token, if any.
83        found: Option<String>,
84    },
85    /// A strict document did not have the required root element.
86    #[error("strict ifcXML requires root `ifcXML`, found {found:?}")]
87    Root {
88        /// First element local name, if any.
89        found: Option<String>,
90    },
91    /// A parsing error with its entity/value location retained.
92    #[error("{path}: {source}")]
93    At {
94        /// XPath-like parser location.
95        path: XmlPath,
96        /// Underlying typed parse error.
97        #[source]
98        source: Box<XmlError>,
99    },
100    /// Writing to the output buffer failed.
101    #[error("write failed: {0}")]
102    Write(String),
103}
104
105impl XmlError {
106    /// Location retained by the reader, when the failure occurred in document content.
107    #[must_use]
108    pub fn path(&self) -> Option<&XmlPath> {
109        match self {
110            Self::At { path, .. } => Some(path),
111            _ => None,
112        }
113    }
114
115    /// Innermost codec error, without discarding the inspectable path.
116    #[must_use]
117    pub fn root_cause(&self) -> &Self {
118        match self {
119            Self::At { source, .. } => source.root_cause(),
120            error => error,
121        }
122    }
123
124    pub(crate) fn at(self, path: String) -> Self {
125        match self {
126            Self::At { .. } => self,
127            source => Self::At {
128                path: XmlPath::new(path),
129                source: Box::new(source),
130            },
131        }
132    }
133}
134
135impl From<quick_xml::Error> for XmlError {
136    fn from(error: quick_xml::Error) -> Self {
137        Self::Malformed(error.to_string())
138    }
139}