ifc_resource/error.rs
1//! Structured failures for resource projections, queries, and authoring.
2
3use ifc_model::EntityId;
4
5/// Result of a resource projection, query, or authoring call, carrying
6/// [`ResourceError`] on failure.
7pub type ResourceResult<T> = Result<T, ResourceError>;
8
9/// Why a resource projection, query, or authoring call could not be
10/// completed.
11#[derive(Debug, Clone, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum ResourceError {
14 /// The model header declares no `FILE_SCHEMA` token.
15 MissingSchema,
16 /// The header declares more than one schema, so the profile is ambiguous.
17 AmbiguousSchema {
18 /// Every schema token found in the header.
19 tokens: Vec<String>,
20 },
21 /// The declared schema is not IFC4 ADD2 TC1 or IFC4X3 ADD2.
22 UnsupportedSchema {
23 /// The rejected schema token.
24 token: String,
25 },
26 /// A requested entity id does not exist in the model.
27 EntityNotFound {
28 /// The missing entity id.
29 id: EntityId,
30 },
31 /// An entity was not the IFC type the caller requested.
32 WrongType {
33 /// The entity that was read.
34 id: EntityId,
35 /// The IFC type the caller expected.
36 expected: &'static str,
37 /// The IFC type actually declared.
38 actual: String,
39 },
40 /// The schema declares no attribute of this name on the entity type.
41 MissingAttribute {
42 /// The entity that was read.
43 entity: EntityId,
44 /// Schema name of the absent attribute.
45 attribute: &'static str,
46 },
47 /// An attribute was present but held the wrong kind of value.
48 InvalidValue {
49 /// The entity that was read.
50 entity: EntityId,
51 /// Schema name of the offending attribute.
52 attribute: &'static str,
53 /// What kind of value was expected.
54 expected: &'static str,
55 },
56 /// An enumeration attribute held a value the schema does not declare.
57 InvalidEnumeration {
58 /// The entity that was read, when the value came from an entity
59 /// rather than an authoring draft.
60 entity: Option<EntityId>,
61 /// Schema name of the offending attribute.
62 attribute: &'static str,
63 /// The undeclared value.
64 value: String,
65 },
66 /// An entity reference attribute pointed at an id the model does not
67 /// contain.
68 DanglingReference {
69 /// The entity holding the dangling reference.
70 entity: EntityId,
71 /// Schema name of the offending attribute.
72 attribute: &'static str,
73 /// The id that resolved to nothing.
74 target: EntityId,
75 },
76 /// An entity reference resolved, but the target is not the IFC type the
77 /// slot requires.
78 WrongReferenceType {
79 /// The entity holding the reference.
80 entity: EntityId,
81 /// Schema name of the offending attribute.
82 attribute: &'static str,
83 /// The referenced entity.
84 target: EntityId,
85 /// The IFC type the slot requires.
86 expected: &'static str,
87 /// The IFC type actually declared.
88 actual: String,
89 },
90 /// An aggregate attribute held fewer items than the schema's minimum
91 /// cardinality.
92 InvalidCardinality {
93 /// The entity that was read.
94 entity: EntityId,
95 /// Schema name of the offending attribute.
96 attribute: &'static str,
97 /// The schema's minimum cardinality.
98 minimum: usize,
99 /// The actual number of items found.
100 actual: usize,
101 },
102 /// A `SET` attribute repeated the same reference more than once.
103 DuplicateReference {
104 /// The entity that was read.
105 entity: EntityId,
106 /// Schema name of the offending attribute.
107 attribute: &'static str,
108 /// The repeated reference.
109 target: EntityId,
110 },
111 /// A WHERE rule or other cross-attribute IFC semantic constraint failed.
112 SemanticViolation {
113 /// The entity that violated the rule, when applicable.
114 entity: Option<EntityId>,
115 /// Name of the violated rule.
116 rule: &'static str,
117 },
118 /// A resource composition or nesting traversal revisited an entity.
119 Cycle {
120 /// The entity where the cycle was detected.
121 at: EntityId,
122 },
123 /// A bounded traversal exceeded its depth or node budget.
124 BudgetExceeded {
125 /// The maximum depth allowed.
126 max_depth: usize,
127 /// The maximum number of nodes allowed.
128 max_nodes: usize,
129 },
130 /// A staged authoring draft could not be committed as written.
131 InvalidDraft {
132 /// The IFC entity type being authored.
133 entity_type: &'static str,
134 /// Schema name of the offending attribute.
135 attribute: &'static str,
136 /// What kind of value was expected.
137 expected: &'static str,
138 },
139 /// A supplied `GlobalId` is not a valid 22-character compressed IFC GUID.
140 InvalidGlobalId,
141 /// A transaction commit conflicted with a concurrent revision.
142 TransactionConflict {
143 /// The revision the transaction expected.
144 expected: u64,
145 /// The revision actually current.
146 actual: u64,
147 },
148}
149
150impl std::fmt::Display for ResourceError {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 match self {
153 Self::MissingSchema => f.write_str("the model header declares no IFC schema"),
154 Self::AmbiguousSchema { tokens } => {
155 write!(f, "the model declares multiple schemas: {tokens:?}")
156 }
157 Self::UnsupportedSchema { token } => {
158 write!(
159 f,
160 "unsupported resource schema `{token}`; expected IFC4 ADD2 TC1 or IFC4X3 ADD2"
161 )
162 }
163 Self::EntityNotFound { id } => write!(f, "entity {id} does not exist"),
164 Self::WrongType {
165 id,
166 expected,
167 actual,
168 } => {
169 write!(f, "entity {id} is {actual}, expected {expected}")
170 }
171 Self::MissingAttribute { entity, attribute } => {
172 write!(f, "entity {entity} has no `{attribute}` slot")
173 }
174 Self::InvalidValue {
175 entity,
176 attribute,
177 expected,
178 } => {
179 write!(
180 f,
181 "entity {entity}.{attribute} is invalid; expected {expected}"
182 )
183 }
184 Self::InvalidEnumeration {
185 entity,
186 attribute,
187 value,
188 } => match entity {
189 Some(entity) => write!(
190 f,
191 "entity {entity}.{attribute} has undeclared enumeration `{value}`"
192 ),
193 None => write!(
194 f,
195 "resource draft.{attribute} has undeclared enumeration `{value}`"
196 ),
197 },
198 Self::DanglingReference {
199 entity,
200 attribute,
201 target,
202 } => {
203 write!(f, "entity {entity}.{attribute} refers to missing {target}")
204 }
205 Self::WrongReferenceType {
206 entity,
207 attribute,
208 target,
209 expected,
210 actual,
211 } => write!(
212 f,
213 "entity {entity}.{attribute} refers to {target} ({actual}), expected {expected}"
214 ),
215 Self::InvalidCardinality {
216 entity,
217 attribute,
218 minimum,
219 actual,
220 } => write!(
221 f,
222 "entity {entity}.{attribute} has {actual} items; minimum is {minimum}"
223 ),
224 Self::DuplicateReference {
225 entity,
226 attribute,
227 target,
228 } => write!(f, "entity {entity}.{attribute} repeats reference {target}"),
229 Self::SemanticViolation { rule, .. } => {
230 write!(f, "resource semantic rule `{rule}` failed")
231 }
232 Self::Cycle { at } => write!(f, "resource composition cycle revisits {at}"),
233 Self::BudgetExceeded {
234 max_depth,
235 max_nodes,
236 } => write!(
237 f,
238 "resource traversal exceeded depth {max_depth} or node budget {max_nodes}"
239 ),
240 Self::InvalidDraft {
241 entity_type,
242 attribute,
243 expected,
244 } => write!(
245 f,
246 "draft {entity_type}.{attribute} is invalid; expected {expected}"
247 ),
248 Self::InvalidGlobalId => {
249 f.write_str("GlobalId is not a valid 22-character compressed IFC GUID")
250 }
251 Self::TransactionConflict { expected, actual } => write!(
252 f,
253 "resource transaction revision conflict: expected {expected}, actual {actual}"
254 ),
255 }
256 }
257}
258
259impl std::error::Error for ResourceError {}