ifc_author/error.rs
1//! Why a construction attempt was refused.
2//!
3//! Every variant names the entity and, where meaningful, the attribute, because
4//! an authoring failure is a programming error in the *calling* application and
5//! the message is the whole diagnostic.
6
7use std::fmt;
8
9use ifc_model::EntityId;
10
11/// A refused construction.
12#[derive(Debug, Clone, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum AuthorError {
15 /// The requested entity is not present in the model snapshot.
16 MissingEntity {
17 /// The missing entity id.
18 id: EntityId,
19 },
20 /// The schema does not declare this entity type.
21 ///
22 /// Reported rather than ignored: silently accepting an unknown type is how
23 /// a typo becomes a file other tools reject.
24 UnknownEntity {
25 /// The requested type name.
26 entity: String,
27 },
28 /// The schema declares the entity, but not this attribute.
29 UnknownAttribute {
30 /// The entity being built.
31 entity: String,
32 /// The attribute name the caller supplied.
33 attribute: String,
34 /// Attribute names the schema does declare, in positional order.
35 known: Vec<String>,
36 },
37 /// A required (non-`OPTIONAL`) attribute was never set.
38 MissingRequired {
39 /// The entity being built.
40 entity: String,
41 /// The unset attribute.
42 attribute: String,
43 },
44 /// The same attribute was set twice.
45 ///
46 /// A silent overwrite hides a copy-paste error in the caller.
47 DuplicateAttribute {
48 /// The entity being built.
49 entity: String,
50 /// The attribute set more than once.
51 attribute: String,
52 },
53 /// The value does not match the attribute's declared type.
54 TypeMismatch {
55 /// The entity being built.
56 entity: String,
57 /// The attribute that was set.
58 attribute: String,
59 /// The type the schema declares.
60 expected: String,
61 /// What the supplied value actually was.
62 found: String,
63 },
64 /// A scalar was supplied where the schema declares an aggregate, or vice
65 /// versa.
66 AggregateMismatch {
67 /// The entity being built.
68 entity: String,
69 /// The attribute that was set.
70 attribute: String,
71 /// Whether the schema declares an aggregate.
72 expected_aggregate: bool,
73 },
74 /// A GlobalId was supplied that is not a valid 22-character IFC GUID.
75 InvalidGlobalId {
76 /// The entity being built.
77 entity: String,
78 /// The rejected text.
79 found: String,
80 },
81 /// A slot the schema derives for this entity was given a value.
82 ///
83 /// A subtype can redeclare an inherited attribute as `DERIVE`d, as
84 /// `IfcSIUnit` does with `Dimensions`. Part 21 writes such a slot as `*`:
85 /// neither a value nor `$` is valid there. The builder writes `*` itself
86 /// when the slot is left unset.
87 DerivedAttribute {
88 /// The entity being built or edited.
89 entity: String,
90 /// The derived attribute.
91 attribute: String,
92 /// What was supplied instead of `*`.
93 found: String,
94 },
95 /// `*` was supplied for a slot the schema does not derive for this entity.
96 NotDerived {
97 /// The entity being built or edited.
98 entity: String,
99 /// The attribute that was set.
100 attribute: String,
101 },
102 /// An existing entity does not have the arity declared by the schema.
103 ArityMismatch {
104 /// The entity being edited.
105 entity: String,
106 /// Number of attributes declared by the schema.
107 expected: usize,
108 /// Number of attributes present in the model.
109 found: usize,
110 },
111 /// A value of the declared type, written in the form ISO 10303-21 does
112 /// not use for it.
113 ///
114 /// A typed parameter (`IFCAREAMEASURE(12.5)`) is written exactly where the
115 /// declared type is a SELECT, which needs it to say which member the
116 /// value is (§12.1.8); everywhere else the bare value is written
117 /// (§12.1.6, §12.1.7). A wrapper naming a different type than the one
118 /// declared is a [`TypeMismatch`](Self::TypeMismatch) instead.
119 ValueForm {
120 /// The entity being built or edited.
121 entity: String,
122 /// The attribute that was set.
123 attribute: String,
124 /// The declared type, or the element type of a declared aggregate.
125 declared: String,
126 /// Whether the declared type requires the typed form: `true` for a
127 /// bare value in a SELECT slot, `false` for a wrapper elsewhere.
128 typed_required: bool,
129 /// What the supplied value actually was.
130 found: String,
131 },
132}
133
134impl fmt::Display for AuthorError {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 match self {
137 Self::MissingEntity { id } => write!(f, "model has no entity `{id}`"),
138 Self::UnknownEntity { entity } => {
139 write!(f, "schema does not declare entity `{entity}`")
140 }
141 Self::UnknownAttribute {
142 entity,
143 attribute,
144 known,
145 } => write!(
146 f,
147 "`{entity}` has no attribute `{attribute}`; declared: {}",
148 known.join(", ")
149 ),
150 Self::MissingRequired { entity, attribute } => write!(
151 f,
152 "`{entity}` requires attribute `{attribute}`, which was not set"
153 ),
154 Self::DuplicateAttribute { entity, attribute } => {
155 write!(f, "`{entity}` attribute `{attribute}` was set twice")
156 }
157 Self::TypeMismatch {
158 entity,
159 attribute,
160 expected,
161 found,
162 } => write!(
163 f,
164 "`{entity}.{attribute}` expects {expected}, found {found}"
165 ),
166 Self::ValueForm {
167 entity,
168 attribute,
169 declared,
170 typed_required: true,
171 found,
172 } => write!(
173 f,
174 "`{entity}.{attribute}` is declared {declared}, a SELECT, so a value \
175 must be written as a typed parameter naming its type; found {found}"
176 ),
177 Self::ValueForm {
178 entity,
179 attribute,
180 declared,
181 typed_required: false,
182 found,
183 } => write!(
184 f,
185 "`{entity}.{attribute}` is declared {declared}, which is not a SELECT, \
186 so its value is written bare; found {found}"
187 ),
188 Self::AggregateMismatch {
189 entity,
190 attribute,
191 expected_aggregate,
192 } => {
193 let (want, got) = if *expected_aggregate {
194 ("an aggregate", "a scalar")
195 } else {
196 ("a scalar", "an aggregate")
197 };
198 write!(f, "`{entity}.{attribute}` expects {want}, found {got}")
199 }
200 Self::InvalidGlobalId { entity, found } => write!(
201 f,
202 "`{entity}.GlobalId` must be a 22-character IFC GUID, found `{found}`"
203 ),
204 Self::DerivedAttribute {
205 entity,
206 attribute,
207 found,
208 } => write!(
209 f,
210 "`{entity}.{attribute}` is derived by the schema and must be written `*`, found {found}"
211 ),
212 Self::NotDerived { entity, attribute } => write!(
213 f,
214 "`{entity}.{attribute}` is not derived by the schema, so it cannot be written `*`"
215 ),
216 Self::ArityMismatch {
217 entity,
218 expected,
219 found,
220 } => write!(
221 f,
222 "`{entity}` declares {expected} attributes, but the model contains {found}"
223 ),
224 }
225 }
226}
227
228impl std::error::Error for AuthorError {}
229
230/// The result of a construction attempt.
231pub type AuthorResult<T> = Result<T, AuthorError>;