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