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 or start gradient
113    /// that differs from where `previous` ends, beyond a magnitude-scaled
114    /// tolerance. A step in height or a kink in grade is refused rather than
115    /// joined, because joining it would shift every downstream height.
116    ProfileDiscontinuity {
117        /// The segment whose start disagrees.
118        entity: EntityId,
119        /// The segment it follows.
120        previous: EntityId,
121        /// Which quantity is discontinuous.
122        seam: ProfileSeam,
123        /// The previous segment's end value, in SI units.
124        expected: f64,
125        /// This segment's authored start value, in SI units.
126        actual: f64,
127    },
128    /// A dangling reference: the target id is not present in the model.
129    DanglingReference {
130        /// The entity holding the dangling reference.
131        entity: EntityId,
132        /// The attribute that names the missing target.
133        attribute: &'static str,
134        /// The id that resolved to nothing.
135        target: EntityId,
136    },
137    /// Graph traversal exceeded an explicit depth or node-count bound.
138    BudgetExceeded {
139        /// The configured maximum traversal depth.
140        max_depth: usize,
141        /// The configured maximum node count.
142        max_nodes: usize,
143    },
144}
145
146/// Which quantity of a vertical profile breaks at a seam.
147#[non_exhaustive]
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum ProfileSeam {
150    /// `StartHeight` differs from the previous segment's end height: a step.
151    Height,
152    /// `StartGradient` differs from the previous `EndGradient`: a kink.
153    Gradient,
154}
155
156impl std::fmt::Display for ProfileSeam {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        f.write_str(match self {
159            Self::Height => "height",
160            Self::Gradient => "gradient",
161        })
162    }
163}
164
165/// Result of an alignment read, carrying [`AlignmentError`] on failure.
166pub type AlignmentResult<T> = Result<T, AlignmentError>;
167
168impl std::fmt::Display for AlignmentError {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        match self {
171            Self::MissingEntity { entity } => write!(f, "missing alignment entity {entity}"),
172            Self::WrongType {
173                entity,
174                expected,
175                actual,
176            } => write!(f, "{entity} is {actual}; expected {expected}"),
177            Self::MissingAttribute {
178                entity,
179                index,
180                name,
181            } => write!(f, "{entity} misses {name} at slot {index}"),
182            Self::InvalidAttribute {
183                entity,
184                index,
185                name,
186            } => write!(f, "{entity} has invalid {name} at slot {index}"),
187            Self::InvalidUnits { detail } => write!(f, "invalid alignment units: {detail}"),
188            Self::InvalidSegment { entity, detail } => {
189                write!(f, "invalid alignment segment {entity}: {detail}")
190            }
191            Self::Unsupported {
192                entity,
193                type_name,
194                detail,
195            } => write!(f, "unsupported {type_name} at {entity}: {detail}"),
196            Self::Graph { detail } => write!(f, "invalid neutral alignment graph: {detail}"),
197            Self::MissingSchema => write!(f, "no FILE_SCHEMA token was declared"),
198            Self::AmbiguousSchema { tokens } => {
199                write!(f, "ambiguous FILE_SCHEMA tokens: {tokens:?}")
200            }
201            Self::UnsupportedSchema { token } => write!(
202                f,
203                "unsupported schema {token}: alignment entities require IFC4X3 ADD2"
204            ),
205            Self::SemanticViolation { entity, rule } => match entity {
206                Some(entity) => write!(f, "{entity} violates {rule}"),
207                None => write!(f, "violates {rule}"),
208            },
209            Self::InvalidAuthoredValue {
210                type_name,
211                attribute,
212                detail,
213            } => write!(f, "cannot author {type_name}.{attribute}: {detail}"),
214            Self::ProfileDiscontinuity {
215                entity,
216                previous,
217                seam,
218                expected,
219                actual,
220            } => write!(
221                f,
222                "vertical {seam} discontinuity at {entity}: starts at {actual}, \
223                 but {previous} ends at {expected}"
224            ),
225            Self::DanglingReference {
226                entity,
227                attribute,
228                target,
229            } => write!(f, "{entity}.{attribute} references missing {target}"),
230            Self::BudgetExceeded {
231                max_depth,
232                max_nodes,
233            } => write!(
234                f,
235                "traversal exceeded bounds (max_depth={max_depth}, max_nodes={max_nodes})"
236            ),
237        }
238    }
239}
240
241impl std::error::Error for AlignmentError {}