Skip to main content

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>;