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 require data this crate does
56    /// not yet combine (e.g. `VIENNESEBEND` needs the cant swing carried by a
57    /// separate `IfcAlignmentCant` layout) or that have no closed-form neutral
58    /// curve here (e.g. `CUBIC`).
59    Unsupported {
60        /// The unsupported entity.
61        entity: EntityId,
62        /// The IFC type or `PredefinedType` name actually declared.
63        type_name: String,
64        /// Why it is refused rather than approximated.
65        detail: &'static str,
66    },
67    /// The assembled neutral alignment graph violates a structural invariant.
68    Graph {
69        /// What expectation failed.
70        detail: String,
71    },
72    /// The model header declares no `FILE_SCHEMA` token.
73    MissingSchema,
74    /// More than one `FILE_SCHEMA` token was declared.
75    AmbiguousSchema {
76        /// Every schema token found in the header.
77        tokens: Vec<String>,
78    },
79    /// The declared schema is not one this crate can interpret.
80    ///
81    /// Alignment entities (`IfcAlignment*`) were introduced in IFC4X3; IFC2X3
82    /// and IFC4 ADD2 TC1 do not declare them at all, so there is no version
83    /// dispatch here the way `ifc-resource`/`ifc-structural` have one --
84    /// exactly one profile is authoritative and anything else is refused.
85    UnsupportedSchema {
86        /// The rejected schema token.
87        token: String,
88    },
89    /// A relationship or nesting structure violates a stated invariant.
90    SemanticViolation {
91        /// The offending entity, when one could be identified.
92        entity: Option<EntityId>,
93        /// The invariant that was violated.
94        rule: &'static str,
95    },
96    /// An authored value was rejected before anything was staged (ADR 0011).
97    ///
98    /// Distinct from [`Self::InvalidSegment`], which describes a segment that
99    /// already exists in a file. Here there is no entity yet and therefore no
100    /// id to report: the caller passed a value that could not be written.
101    InvalidAuthoredValue {
102        /// The IFC type being authored.
103        type_name: &'static str,
104        /// The attribute whose value was rejected.
105        attribute: &'static str,
106        /// Why it cannot be written.
107        detail: String,
108    },
109    /// Two consecutive vertical segments disagree where they meet.
110    ///
111    /// The segment at `entity` restates a start height or start gradient
112    /// that differs from where `previous` ends, beyond a magnitude-scaled
113    /// tolerance. A step in height or a kink in grade is refused rather than
114    /// joined, because joining it would shift every downstream height.
115    ProfileDiscontinuity {
116        /// The segment whose start disagrees.
117        entity: EntityId,
118        /// The segment it follows.
119        previous: EntityId,
120        /// Which quantity is discontinuous.
121        seam: ProfileSeam,
122        /// The previous segment's end value, in SI units.
123        expected: f64,
124        /// This segment's authored start value, in SI units.
125        actual: f64,
126    },
127    /// A dangling reference: the target id is not present in the model.
128    DanglingReference {
129        /// The entity holding the dangling reference.
130        entity: EntityId,
131        /// The attribute that names the missing target.
132        attribute: &'static str,
133        /// The id that resolved to nothing.
134        target: EntityId,
135    },
136    /// Graph traversal exceeded an explicit depth or node-count bound.
137    BudgetExceeded {
138        /// The configured maximum traversal depth.
139        max_depth: usize,
140        /// The configured maximum node count.
141        max_nodes: usize,
142    },
143}
144
145/// Which quantity of a vertical profile breaks at a seam.
146#[non_exhaustive]
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum ProfileSeam {
149    /// `StartHeight` differs from the previous segment's end height: a step.
150    Height,
151    /// `StartGradient` differs from the previous `EndGradient`: a kink.
152    Gradient,
153}
154
155impl std::fmt::Display for ProfileSeam {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        f.write_str(match self {
158            Self::Height => "height",
159            Self::Gradient => "gradient",
160        })
161    }
162}
163
164/// Result of an alignment read, carrying [`AlignmentError`] on failure.
165pub type AlignmentResult<T> = Result<T, AlignmentError>;
166
167impl std::fmt::Display for AlignmentError {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        match self {
170            Self::MissingEntity { entity } => write!(f, "missing alignment entity {entity}"),
171            Self::WrongType {
172                entity,
173                expected,
174                actual,
175            } => write!(f, "{entity} is {actual}; expected {expected}"),
176            Self::MissingAttribute {
177                entity,
178                index,
179                name,
180            } => write!(f, "{entity} misses {name} at slot {index}"),
181            Self::InvalidAttribute {
182                entity,
183                index,
184                name,
185            } => write!(f, "{entity} has invalid {name} at slot {index}"),
186            Self::InvalidUnits { detail } => write!(f, "invalid alignment units: {detail}"),
187            Self::InvalidSegment { entity, detail } => {
188                write!(f, "invalid alignment segment {entity}: {detail}")
189            }
190            Self::Unsupported {
191                entity,
192                type_name,
193                detail,
194            } => write!(f, "unsupported {type_name} at {entity}: {detail}"),
195            Self::Graph { detail } => write!(f, "invalid neutral alignment graph: {detail}"),
196            Self::MissingSchema => write!(f, "no FILE_SCHEMA token was declared"),
197            Self::AmbiguousSchema { tokens } => {
198                write!(f, "ambiguous FILE_SCHEMA tokens: {tokens:?}")
199            }
200            Self::UnsupportedSchema { token } => write!(
201                f,
202                "unsupported schema {token}: alignment entities require IFC4X3 ADD2"
203            ),
204            Self::SemanticViolation { entity, rule } => match entity {
205                Some(entity) => write!(f, "{entity} violates {rule}"),
206                None => write!(f, "violates {rule}"),
207            },
208            Self::InvalidAuthoredValue {
209                type_name,
210                attribute,
211                detail,
212            } => write!(f, "cannot author {type_name}.{attribute}: {detail}"),
213            Self::ProfileDiscontinuity {
214                entity,
215                previous,
216                seam,
217                expected,
218                actual,
219            } => write!(
220                f,
221                "vertical {seam} discontinuity at {entity}: starts at {actual}, \
222                 but {previous} ends at {expected}"
223            ),
224            Self::DanglingReference {
225                entity,
226                attribute,
227                target,
228            } => write!(f, "{entity}.{attribute} references missing {target}"),
229            Self::BudgetExceeded {
230                max_depth,
231                max_nodes,
232            } => write!(
233                f,
234                "traversal exceeded bounds (max_depth={max_depth}, max_nodes={max_nodes})"
235            ),
236        }
237    }
238}
239
240impl std::error::Error for AlignmentError {}