ifc_georef/error.rs
1//! Structured failures while resolving an IFC coordinate operation.
2
3use ifc_model::EntityId;
4
5/// Why a project-to-map operation could not be resolved losslessly.
6#[non_exhaustive]
7#[derive(Debug, Clone, PartialEq)]
8pub enum GeorefError {
9 /// An entity referenced another that the model does not contain.
10 MissingEntity {
11 /// The entity holding the dangling reference.
12 referrer: EntityId,
13 /// The id that resolved to nothing.
14 missing: EntityId,
15 },
16 /// An entity was not the IFC type the referencing slot requires.
17 WrongType {
18 /// The entity that was read.
19 entity: EntityId,
20 /// The IFC type the slot requires.
21 expected: &'static str,
22 /// The IFC type actually declared.
23 actual: String,
24 },
25 /// A mandatory attribute was absent, so the value cannot be inferred.
26 MissingAttribute {
27 /// The entity that was read.
28 entity: EntityId,
29 /// Zero-based slot index of the absent attribute.
30 index: usize,
31 /// Schema name of the attribute.
32 name: &'static str,
33 },
34 /// A value supplied to an authoring helper was not acceptable.
35 ///
36 /// Distinct from [`GeorefError::InvalidAttribute`], which names an
37 /// entity already in the model: at authoring time there is no id
38 /// yet, so the entity is named by type.
39 AuthoringInvalid {
40 /// The entity being authored.
41 entity: &'static str,
42 /// The attribute at fault.
43 attribute: &'static str,
44 /// What was supplied.
45 value: String,
46 },
47 /// An attribute was present but held the wrong kind of value.
48 InvalidAttribute {
49 /// The entity that was read.
50 entity: EntityId,
51 /// Zero-based slot index of the offending attribute.
52 index: usize,
53 /// Schema name of the attribute.
54 name: &'static str,
55 },
56 /// The coordinate operation is a subtype this crate does not resolve.
57 ///
58 /// Refused rather than approximated: a non-projected operation would
59 /// silently produce wrong map coordinates.
60 UnsupportedOperation {
61 /// The coordinate operation entity.
62 entity: EntityId,
63 /// The IFC type actually declared.
64 actual: String,
65 },
66 /// The map x axis is zero-length or non-finite, so no rotation exists.
67 DegenerateAxis {
68 /// The map conversion entity.
69 entity: EntityId,
70 },
71 /// The map scale is zero, negative, or non-finite.
72 InvalidScale {
73 /// The map conversion entity.
74 entity: EntityId,
75 /// The rejected scale.
76 value: f64,
77 },
78 /// The map unit is not a length unit this crate can reduce to metres.
79 InvalidUnit {
80 /// The unit entity.
81 entity: EntityId,
82 /// Which expectation failed.
83 detail: &'static str,
84 },
85 /// A unit conversion chain revisited an entity or ran deeper than 16.
86 ///
87 /// Both guards exist: a repeated id catches a true loop, the depth cap
88 /// catches an unbounded chain of distinct units.
89 UnitCycle {
90 /// The unit entity where the walk was abandoned.
91 entity: EntityId,
92 },
93 /// The model header declares no `FILE_SCHEMA` token.
94 MissingSchema,
95 /// The header declares more than one schema, so the profile is ambiguous.
96 ///
97 /// Refused rather than picking one: the choice changes which coordinate
98 /// operations are legal.
99 AmbiguousSchema {
100 /// Every schema token found in the header.
101 tokens: Vec<String>,
102 },
103 /// The declared schema has no projected georeferencing profile here.
104 ///
105 /// IFC2X3 declares no georeferencing entities at all; other tokens may be
106 /// unrecognised.
107 UnsupportedSchema {
108 /// The rejected schema token.
109 token: String,
110 },
111 /// Chaining onto a project frame requires that frame to be supplied.
112 MissingProjectFrame,
113 /// The supplied project frame is not an invertible rigid transform.
114 DegenerateProjectFrame,
115 /// A direction was zero-length or non-finite, so it cannot be normalised.
116 NonFiniteDirection {
117 /// The direction entity.
118 entity: EntityId,
119 },
120}
121
122impl std::fmt::Display for GeorefError {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 match self {
125 Self::MissingEntity { referrer, missing } => {
126 write!(f, "{referrer} references missing {missing}")
127 }
128 Self::WrongType {
129 entity,
130 expected,
131 actual,
132 } => write!(f, "{entity} is {actual}, expected {expected}"),
133 Self::MissingAttribute {
134 entity,
135 index,
136 name,
137 } => write!(f, "{entity} is missing {name} at slot {index}"),
138 Self::InvalidAttribute {
139 entity,
140 index,
141 name,
142 } => write!(f, "{entity} has invalid {name} at slot {index}"),
143 Self::AuthoringInvalid {
144 entity,
145 attribute,
146 value,
147 } => write!(f, "{entity}.{attribute}: {value}"),
148 Self::UnsupportedOperation { entity, actual } => {
149 write!(f, "{entity} uses unsupported coordinate operation {actual}")
150 }
151 Self::DegenerateAxis { entity } => {
152 write!(f, "{entity} has a zero-length or non-finite map x axis")
153 }
154 Self::InvalidScale { entity, value } => {
155 write!(f, "{entity} has invalid map scale {value}")
156 }
157 Self::InvalidUnit { entity, detail } => {
158 write!(f, "{entity} has unsupported or invalid map unit: {detail}")
159 }
160 Self::UnitCycle { entity } => {
161 write!(f, "unit conversion chain at {entity} is cyclic or too deep")
162 }
163 Self::MissingSchema => {
164 write!(f, "model header declares no FILE_SCHEMA token")
165 }
166 Self::AmbiguousSchema { tokens } => {
167 write!(f, "model header declares multiple schemas: {tokens:?}")
168 }
169 Self::UnsupportedSchema { token } => {
170 write!(
171 f,
172 "schema {token} does not declare IFC georeferencing entities \
173 or its coordinate-operation profile is not projected here"
174 )
175 }
176 Self::MissingProjectFrame => {
177 write!(
178 f,
179 "chaining a project-to-map operation onto a project frame \
180 requires that frame to be supplied explicitly"
181 )
182 }
183 Self::DegenerateProjectFrame => {
184 write!(
185 f,
186 "supplied project frame is not an invertible rigid transform \
187 (zero or non-finite determinant)"
188 )
189 }
190 Self::NonFiniteDirection { entity } => {
191 write!(f, "{entity} has a non-finite or zero-length direction")
192 }
193 }
194 }
195}
196
197impl std::error::Error for GeorefError {}
198
199/// Result of a georeferencing read, carrying [`GeorefError`] on failure.
200pub type GeorefResult<T> = Result<T, GeorefError>;