ifc_alignment/error.rs
1//! Structured alignment interpretation failures.
2
3use ifc_model::EntityId;
4
5/// Why an IFC alignment entity could not be read or lowered.
6#[non_exhaustive]
7#[derive(Debug, Clone, PartialEq)]
8pub enum AlignmentError {
9 /// A referenced entity id is not present in the model.
10 MissingEntity {
11 /// The id that resolved to nothing.
12 entity: EntityId,
13 },
14 /// An entity was not the IFC type the referencing slot requires.
15 WrongType {
16 /// The entity that was read.
17 entity: EntityId,
18 /// The IFC type the slot requires.
19 expected: &'static str,
20 /// The IFC type actually declared.
21 actual: String,
22 },
23 /// A mandatory attribute was absent, so the value cannot be inferred.
24 MissingAttribute {
25 /// The entity that was read.
26 entity: EntityId,
27 /// Zero-based slot index of the absent attribute.
28 index: usize,
29 /// Schema name of the attribute.
30 name: &'static str,
31 },
32 /// An attribute was present but held the wrong kind of value.
33 InvalidAttribute {
34 /// The entity that was read.
35 entity: EntityId,
36 /// Zero-based slot index of the offending attribute.
37 index: usize,
38 /// Schema name of the attribute.
39 name: &'static str,
40 },
41 /// The project's unit assignment does not resolve to usable lengths or angles.
42 InvalidUnits {
43 /// What expectation failed.
44 detail: &'static str,
45 },
46 /// A horizontal, vertical, or cant segment carries internally inconsistent data.
47 InvalidSegment {
48 /// The segment entity.
49 entity: EntityId,
50 /// What expectation failed.
51 detail: &'static str,
52 },
53 /// A segment type or configuration this crate deliberately does not lower.
54 ///
55 /// Covers segment kinds that are legal IFC but lack the data they need in
56 /// this call (e.g. a `VIENNESEBEND` lowered without its `IfcAlignmentCant`
57 /// layout, its `GravityCenterLineHeight`, or the station a chained layout
58 /// supplies) or that have no closed-form neutral curve here (e.g.
59 /// `CUBIC`).
60 Unsupported {
61 /// The unsupported entity.
62 entity: EntityId,
63 /// The IFC type or `PredefinedType` name actually declared.
64 type_name: String,
65 /// Why it is refused rather than approximated.
66 detail: &'static str,
67 },
68 /// The assembled neutral alignment graph violates a structural invariant.
69 Graph {
70 /// What expectation failed.
71 detail: String,
72 },
73 /// The model header declares no `FILE_SCHEMA` token.
74 MissingSchema,
75 /// More than one `FILE_SCHEMA` token was declared.
76 AmbiguousSchema {
77 /// Every schema token found in the header.
78 tokens: Vec<String>,
79 },
80 /// The declared schema is not one this crate can interpret.
81 ///
82 /// Alignment entities (`IfcAlignment*`) were introduced in IFC4X3; IFC2X3
83 /// and IFC4 ADD2 TC1 do not declare them at all, so there is no version
84 /// dispatch here the way `ifc-resource`/`ifc-structural` have one --
85 /// exactly one profile is authoritative and anything else is refused.
86 UnsupportedSchema {
87 /// The rejected schema token.
88 token: String,
89 },
90 /// A relationship or nesting structure violates a stated invariant.
91 SemanticViolation {
92 /// The offending entity, when one could be identified.
93 entity: Option<EntityId>,
94 /// The invariant that was violated.
95 rule: &'static str,
96 },
97 /// An authored value was rejected before anything was staged (ADR 0011).
98 ///
99 /// Distinct from [`Self::InvalidSegment`], which describes a segment that
100 /// already exists in a file. Here there is no entity yet and therefore no
101 /// id to report: the caller passed a value that could not be written.
102 InvalidAuthoredValue {
103 /// The IFC type being authored.
104 type_name: &'static str,
105 /// The attribute whose value was rejected.
106 attribute: &'static str,
107 /// Why it cannot be written.
108 detail: String,
109 },
110 /// Two consecutive vertical segments disagree where they meet.
111 ///
112 /// The segment at `entity` restates a start height that differs from
113 /// where `previous` ends, beyond the seam tolerance: a step, refused
114 /// rather than joined because joining it would shift every downstream
115 /// height. A grade break at a height-continuous seam is legal IFC4.3 and
116 /// is reported as [`ProfileSeam::Gradient`] only when tangency is asked
117 /// for explicitly (`VerticalLayout::require_tangential`).
118 ProfileDiscontinuity {
119 /// The segment whose start disagrees.
120 entity: EntityId,
121 /// The segment it follows.
122 previous: EntityId,
123 /// Which quantity is discontinuous.
124 seam: ProfileSeam,
125 /// The previous segment's end value, in SI units.
126 expected: f64,
127 /// This segment's authored start value, in SI units.
128 actual: f64,
129 },
130 /// A dangling reference: the target id is not present in the model.
131 DanglingReference {
132 /// The entity holding the dangling reference.
133 entity: EntityId,
134 /// The attribute that names the missing target.
135 attribute: &'static str,
136 /// The id that resolved to nothing.
137 target: EntityId,
138 },
139 /// A distance along or a station value outside the range a mapping
140 /// covers, such as a distance before an alignment's first stationing
141 /// referent.
142 OutOfRange {
143 /// The entity whose mapping was queried (the `IfcAlignment` for
144 /// stationing).
145 entity: EntityId,
146 /// Which quantity was out of range.
147 quantity: &'static str,
148 /// The value queried, in SI units.
149 value: f64,
150 },
151 /// A station value that labels more than one distance along, as after
152 /// a station equation that steps back. Picking one would be a guess.
153 AmbiguousStation {
154 /// The `IfcAlignment` whose stationing was queried.
155 alignment: EntityId,
156 /// The station value queried, in metres.
157 station: f64,
158 /// Every distance along it labels, ascending, in metres.
159 distances: Vec<f64>,
160 },
161 /// Graph traversal exceeded an explicit depth or node-count bound.
162 BudgetExceeded {
163 /// The configured maximum traversal depth.
164 max_depth: usize,
165 /// The configured maximum node count.
166 max_nodes: usize,
167 },
168}
169
170/// Which quantity of a vertical profile breaks at a seam.
171#[non_exhaustive]
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub enum ProfileSeam {
174 /// `StartHeight` differs from the previous segment's end height: a step.
175 Height,
176 /// `StartGradient` differs from the previous `EndGradient`: a grade
177 /// break. Legal IFC4.3; produced only by an explicit tangency request
178 /// (`VerticalLayout::require_tangential`), never by reading or lowering
179 /// a profile.
180 Gradient,
181}
182
183impl std::fmt::Display for ProfileSeam {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 f.write_str(match self {
186 Self::Height => "height",
187 Self::Gradient => "gradient",
188 })
189 }
190}
191
192/// Result of an alignment read, carrying [`AlignmentError`] on failure.
193pub type AlignmentResult<T> = Result<T, AlignmentError>;
194
195impl std::fmt::Display for AlignmentError {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 match self {
198 Self::MissingEntity { entity } => write!(f, "missing alignment entity {entity}"),
199 Self::WrongType {
200 entity,
201 expected,
202 actual,
203 } => write!(f, "{entity} is {actual}; expected {expected}"),
204 Self::MissingAttribute {
205 entity,
206 index,
207 name,
208 } => write!(f, "{entity} misses {name} at slot {index}"),
209 Self::InvalidAttribute {
210 entity,
211 index,
212 name,
213 } => write!(f, "{entity} has invalid {name} at slot {index}"),
214 Self::InvalidUnits { detail } => write!(f, "invalid alignment units: {detail}"),
215 Self::InvalidSegment { entity, detail } => {
216 write!(f, "invalid alignment segment {entity}: {detail}")
217 }
218 Self::Unsupported {
219 entity,
220 type_name,
221 detail,
222 } => write!(f, "unsupported {type_name} at {entity}: {detail}"),
223 Self::Graph { detail } => write!(f, "invalid neutral alignment graph: {detail}"),
224 Self::MissingSchema => write!(f, "no FILE_SCHEMA token was declared"),
225 Self::AmbiguousSchema { tokens } => {
226 write!(f, "ambiguous FILE_SCHEMA tokens: {tokens:?}")
227 }
228 Self::UnsupportedSchema { token } => write!(
229 f,
230 "unsupported schema {token}: alignment entities require IFC4X3 ADD2"
231 ),
232 Self::SemanticViolation { entity, rule } => match entity {
233 Some(entity) => write!(f, "{entity} violates {rule}"),
234 None => write!(f, "violates {rule}"),
235 },
236 Self::InvalidAuthoredValue {
237 type_name,
238 attribute,
239 detail,
240 } => write!(f, "cannot author {type_name}.{attribute}: {detail}"),
241 Self::ProfileDiscontinuity {
242 entity,
243 previous,
244 seam,
245 expected,
246 actual,
247 } => write!(
248 f,
249 "vertical {seam} discontinuity at {entity}: starts at {actual}, \
250 but {previous} ends at {expected}"
251 ),
252 Self::DanglingReference {
253 entity,
254 attribute,
255 target,
256 } => write!(f, "{entity}.{attribute} references missing {target}"),
257 Self::OutOfRange {
258 entity,
259 quantity,
260 value,
261 } => write!(
262 f,
263 "{quantity} {value} lies outside the range {entity} covers"
264 ),
265 Self::AmbiguousStation {
266 alignment,
267 station,
268 distances,
269 } => write!(
270 f,
271 "station {station} of {alignment} labels several distances along: {distances:?}"
272 ),
273 Self::BudgetExceeded {
274 max_depth,
275 max_nodes,
276 } => write!(
277 f,
278 "traversal exceeded bounds (max_depth={max_depth}, max_nodes={max_nodes})"
279 ),
280 }
281 }
282}
283
284impl std::error::Error for AlignmentError {}