Skip to main content

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