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, Clone, 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 /// An element names an entity type the schema does not declare.
104 #[error("`{name}` is not an entity the schema declares")]
105 UnknownEntity {
106 /// The element or type name as written.
107 name: String,
108 },
109 /// An abstract entity was instantiated.
110 #[error("`{name}` is abstract and cannot be instantiated")]
111 AbstractEntity {
112 /// The declared entity name.
113 name: String,
114 },
115 /// An XML attribute or child element that no attribute of the entity
116 /// declares. Never placed into a free slot.
117 #[error(
118 "{entity} has no attribute `{attribute}` (on element `{element}`); \
119 refusing to guess its slot"
120 )]
121 UnknownAttribute {
122 /// The entity type, as the schema declares it.
123 entity: String,
124 /// The XML element carrying it: the entity's own element, which is
125 /// an attribute name when the entity is defined in place.
126 element: String,
127 /// The XML attribute or child element name.
128 attribute: String,
129 },
130 /// A value whose kind the declared type does not admit: an integer in a
131 /// label, a reference where a measure is declared, an entity or typed
132 /// value outside its SELECT.
133 #[error("declared {declared}, found {found}")]
134 TypeMismatch {
135 /// The declared type, in EXPRESS terms.
136 declared: String,
137 /// What the document holds, in words.
138 found: String,
139 },
140 /// An attribute written in a form its declared type does not take in
141 /// this layout: a simple value as a child element, or an entity
142 /// reference as an XML attribute.
143 #[error("attribute `{attribute}` must be {expected}, found {found}")]
144 WrongForm {
145 /// The attribute name.
146 attribute: String,
147 /// The form the layout requires.
148 expected: &'static str,
149 /// The form the document used.
150 found: &'static str,
151 },
152 /// A reference to an id no element of the document defines.
153 #[error("reference to undefined id {id:?}")]
154 UnresolvedReference {
155 /// The referenced id as written.
156 id: String,
157 },
158 /// Two elements define the same id.
159 #[error("id {id:?} is defined twice")]
160 DuplicateId {
161 /// The repeated id.
162 id: String,
163 },
164 /// An inverse attribute implies a value its relationship contradicts.
165 #[error(
166 "inverse `{inverse}` places this entity in `{relationship}.{attribute}`, \
167 which {detail}"
168 )]
169 InverseConflict {
170 /// The inverse attribute's name.
171 inverse: String,
172 /// The relationship entity's type.
173 relationship: String,
174 /// The relationship's explicit attribute the inverse inverts.
175 attribute: String,
176 /// Why the implied value cannot be placed.
177 detail: String,
178 },
179 /// The schema given does not describe the selected release profile.
180 #[error("ifcXML profile {profile} needs the {profile} schema, given `{schema}`")]
181 SchemaMismatch {
182 /// The profile's schema token.
183 profile: &'static str,
184 /// The given schema's name.
185 schema: String,
186 },
187 /// Valid content this reader or writer does not implement. Refused,
188 /// never approximated.
189 #[error("unsupported: {construct}")]
190 Unsupported {
191 /// What was met, in words.
192 construct: String,
193 },
194}
195
196impl XmlError {
197 /// Location retained by the reader, when the failure occurred in document content.
198 #[must_use]
199 pub fn path(&self) -> Option<&XmlPath> {
200 match self {
201 Self::At { path, .. } => Some(path),
202 _ => None,
203 }
204 }
205
206 /// Innermost codec error, without discarding the inspectable path.
207 #[must_use]
208 pub fn root_cause(&self) -> &Self {
209 match self {
210 Self::At { source, .. } => source.root_cause(),
211 error => error,
212 }
213 }
214
215 pub(crate) fn at(self, path: String) -> Self {
216 match self {
217 Self::At { .. } => self,
218 source => Self::At {
219 path: XmlPath::new(path),
220 source: Box::new(source),
221 },
222 }
223 }
224}
225
226impl From<quick_xml::Error> for XmlError {
227 fn from(error: quick_xml::Error) -> Self {
228 Self::Malformed(error.to_string())
229 }
230}