Skip to main content

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 {}