Skip to main content

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>;