egml_core/error.rs
1use crate::model::base::Id;
2use crate::model::geometry::DirectPosition;
3use std::fmt;
4
5/// Errors returned by `egml-core` operations.
6#[derive(Debug, PartialEq, Clone)]
7#[non_exhaustive]
8pub enum Error {
9 /// Returned when a floating-point coordinate is not finite (NaN or ±infinity).
10 ///
11 /// `axis` names the offending component (`"x"`, `"y"`, or `"z"`); `value` is
12 /// the actual non-finite number that was supplied.
13 NonFiniteCoordinate { axis: &'static str, value: f64 },
14
15 /// Returned when a collection has fewer elements than the minimum required by
16 /// the GML geometry constraint.
17 ///
18 /// Optional fields carry progressively more context: `spec` cites the ISO
19 /// clause, `id` names the offending GML object, and `detail` gives a
20 /// human-readable description of the actual content that was supplied.
21 TooFewElements {
22 geometry: &'static str,
23 minimum: usize,
24 spec: Option<&'static str>,
25 id: Option<Id>,
26 detail: Option<String>,
27 },
28
29 /// Returned when a collection has a number of elements other than the exact
30 /// count required by the GML geometry constraint.
31 ///
32 /// `expected` is the required count; `actual` is what was supplied.
33 /// `spec` optionally cites the ISO/OGC clause that mandates the count.
34 InvalidElementCount {
35 geometry: &'static str,
36 expected: usize,
37 actual: usize,
38 spec: Option<&'static str>,
39 },
40
41 /// Returned when two positions that must be distinct are identical.
42 ///
43 /// Applies to [`Triangle`](crate::model::geometry::primitives::Triangle)
44 /// vertices ([OGC 07-036 §10.5.12.5](https://docs.ogc.org/is/07-036/07-036.pdf)).
45 IdenticalPositions {
46 first: DirectPosition,
47 second: DirectPosition,
48 },
49
50 /// Returned when adjacent positions in a sequence are equal.
51 ///
52 /// `index` is the zero-based index of the first position in the duplicate
53 /// pair; `position` is its value. Applies to
54 /// [`LinearRing`](crate::model::geometry::primitives::LinearRing)
55 /// ([OGC 07-036 §10.5.8](https://docs.ogc.org/is/07-036/07-036.pdf)) and
56 /// [`LineString`](crate::model::geometry::primitives::LineString)
57 /// ([OGC 07-036 §10.4.4](https://docs.ogc.org/is/07-036/07-036.pdf)).
58 AdjacentDuplicatePositions {
59 index: usize,
60 position: DirectPosition,
61 },
62
63 /// Returned when the first and last position of a ring are equal.
64 ///
65 /// `position` is the repeated vertex. A `gml:LinearRing` is implicitly
66 /// closed and must not include an explicit closing vertex ([OGC 07-036 §10.5.8](https://docs.ogc.org/is/07-036/07-036.pdf)).
67 RepeatedClosingVertex { position: DirectPosition },
68
69 /// Returned when an [`Envelope`](crate::model::geometry::Envelope) is constructed
70 /// with a lower corner that strictly exceeds the upper corner along `axis`.
71 ///
72 /// `lower` and `upper` are the actual conflicting coordinate values.
73 /// [OGC 07-036 §10.1.4.6](https://docs.ogc.org/is/07-036/07-036.pdf) requires `lowerCorner ≤ upperCorner` in every component.
74 InvalidEnvelopeBounds {
75 axis: &'static str,
76 lower: f64,
77 upper: f64,
78 },
79
80 /// Returned when
81 /// [`Envelope::to_triangulated_surface`](crate::model::geometry::Envelope::to_triangulated_surface)
82 /// is called on an envelope that is neither a surface nor a volume.
83 ///
84 /// `non_zero_extents` is the number of axes with non-zero extent (0 or 1).
85 NotSurfaceOrVolume { non_zero_extents: u8 },
86
87 /// Returned when [`Envelope::to_polygon`](crate::model::geometry::Envelope::to_polygon)
88 /// is called on an envelope that does not have exactly two non-zero extents.
89 ///
90 /// `non_zero_extents` is the actual number of axes with non-zero extent.
91 NotASurface { non_zero_extents: u8 },
92
93 /// Returned when [`Envelope::to_solid`](crate::model::geometry::Envelope::to_solid)
94 /// is called on an envelope that does not have all three extents non-zero.
95 ///
96 /// `non_zero_extents` is the actual number of axes with non-zero extent.
97 NotAVolume { non_zero_extents: u8 },
98
99 /// Returned when an empty string is used to construct a [`Id`](crate::model::base::Id).
100 EmptyId,
101
102 /// Returned when the earcut polygon triangulation algorithm produces no triangles.
103 ///
104 /// `context` provides additional information about which polygon or patch
105 /// could not be decomposed.
106 TriangulationFailed { context: String },
107
108 /// Returned when an operation requires an exterior ring but the polygon has none.
109 MissingExteriorRing,
110
111 /// Returned when a surface property carries only an xlink:href reference and the
112 /// referenced geometry object has not been resolved into an inline object.
113 ///
114 /// `href` is the reference value if one was present, or `None` if the property
115 /// has neither an inline object nor a reference.
116 UnresolvedSurfaceReference { href: Option<String> },
117
118 /// Returned when a curve property carries only an xlink:href reference and the
119 /// referenced geometry object has not been resolved into an inline object.
120 ///
121 /// `href` is the reference value if one was present, or `None` if the property
122 /// has neither an inline object nor a reference.
123 UnresolvedCurveReference { href: Option<String> },
124
125 /// Returned when an operation requires an exterior shell but the solid has none.
126 MissingExteriorShell,
127
128 /// Returned when `triangulate` is called on a geometry type that cannot
129 /// produce a surface (e.g. `Point`, `MultiCurve`).
130 ///
131 /// `geometry` names the concrete variant that was encountered.
132 TriangulationNotSupported { geometry: &'static str },
133
134 /// Returned when an attribute held a value that could not be parsed into
135 /// its expected type (e.g. an `xlink:show`/`xlink:actuate` value outside
136 /// the enumeration defined by the XLink specification).
137 ///
138 /// `attribute` names the attribute (e.g. `"xlink:show"`); `value` is the
139 /// raw string that was supplied.
140 InvalidAttributeValue {
141 attribute: &'static str,
142 value: String,
143 },
144
145 /// Returned when a timezone offset is not a whole number of minutes or lies
146 /// outside the range `-14:00..=+14:00` permitted by
147 /// [XSD 1.1 Part 2 §3.3.7](https://www.w3.org/TR/xmlschema11-2/#dateTime).
148 ///
149 /// `seconds` is the offset east of UTC that was supplied.
150 InvalidTimezoneOffset { seconds: i32 },
151
152 /// Returned when a local date-time cannot be represented in UTC with the
153 /// given offset because it lies at the edge of the supported range.
154 ///
155 /// `value` describes the local date-time and offset that were supplied.
156 DateTimeOutOfRange { value: String },
157
158 /// Returned when date or time fields do not form a valid calendar value,
159 /// e.g. February 30, hour 24 or minute 60.
160 ///
161 /// `value` lists the fields that were supplied.
162 InvalidDateTimeFields { value: String },
163}
164
165impl fmt::Display for Error {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 match self {
168 Error::NonFiniteCoordinate { axis, value } => write!(
169 f,
170 "coordinate '{axis}' has non-finite value {value}; \
171 all GML coordinate values must be real numbers (OGC 07-036 §10.1.4.1)"
172 ),
173 Error::TooFewElements {
174 geometry,
175 minimum,
176 spec,
177 id,
178 detail,
179 } => {
180 write!(f, "{geometry} requires at least {minimum} element(s)")?;
181 if let Some(s) = spec {
182 write!(f, " ({s})")?;
183 }
184 if let Some(i) = id {
185 write!(f, " [id={i}]")?;
186 }
187 if let Some(d) = detail {
188 write!(f, ": {d}")?;
189 }
190 Ok(())
191 }
192 Error::InvalidElementCount {
193 geometry,
194 expected,
195 actual,
196 spec,
197 } => {
198 write!(
199 f,
200 "{geometry} requires exactly {expected} element(s), got {actual}"
201 )?;
202 if let Some(s) = spec {
203 write!(f, " ({s})")?;
204 }
205 Ok(())
206 }
207 Error::IdenticalPositions { first, second } => write!(
208 f,
209 "geometry contains two identical positions {first} and {second}; \
210 all positions must be distinct (OGC 07-036 §10.5.12.5)"
211 ),
212 Error::AdjacentDuplicatePositions { index, position } => write!(
213 f,
214 "adjacent duplicate positions at index {index} ({position}); \
215 consecutive coordinates must be distinct"
216 ),
217 Error::RepeatedClosingVertex { position } => write!(
218 f,
219 "linear ring has repeated closing vertex at {position}; \
220 gml:LinearRing is implicitly closed and must not include \
221 an explicit closing vertex (OGC 07-036 §10.5.8)"
222 ),
223 Error::InvalidEnvelopeBounds { axis, lower, upper } => write!(
224 f,
225 "envelope lower.{axis}={lower} exceeds upper.{axis}={upper}; \
226 lowerCorner must be ≤ upperCorner in every component (OGC 07-036 §10.1.4.6)"
227 ),
228 Error::NotSurfaceOrVolume { non_zero_extents } => write!(
229 f,
230 "envelope has {non_zero_extents} non-zero extent(s) and cannot be triangulated; \
231 requires a surface (2 non-zero extents) or volume (3 non-zero extents)"
232 ),
233 Error::NotASurface { non_zero_extents } => write!(
234 f,
235 "envelope has {non_zero_extents} non-zero extent(s); \
236 to_polygon requires exactly 2"
237 ),
238 Error::NotAVolume { non_zero_extents } => write!(
239 f,
240 "envelope has {non_zero_extents} non-zero extent(s); \
241 to_solid requires all 3 to be non-zero"
242 ),
243 Error::EmptyId => write!(f, "gml:id must not be empty"),
244 Error::TriangulationFailed { context } => {
245 write!(f, "polygon triangulation (earcut) failed: {context}")
246 }
247 Error::MissingExteriorRing => write!(
248 f,
249 "polygon has no exterior ring; \
250 operation requires a defined outer boundary (OGC 07-036 §10.5.6)"
251 ),
252 Error::UnresolvedSurfaceReference { href: Some(href) } => write!(
253 f,
254 "surface property references '{href}' via xlink:href but the object has not been resolved"
255 ),
256 Error::UnresolvedSurfaceReference { href: None } => write!(
257 f,
258 "surface property has neither an inline object nor an xlink:href reference"
259 ),
260 Error::UnresolvedCurveReference { href: Some(href) } => write!(
261 f,
262 "curve property references '{href}' via xlink:href but the object has not been resolved"
263 ),
264 Error::UnresolvedCurveReference { href: None } => write!(
265 f,
266 "curve property has neither an inline object nor an xlink:href reference"
267 ),
268 Error::MissingExteriorShell => write!(
269 f,
270 "solid has no exterior shell; \
271 operation requires a defined outer boundary (OGC 07-036 §10.6.4)"
272 ),
273 Error::TriangulationNotSupported { geometry } => write!(
274 f,
275 "triangulation is not supported for geometry type '{geometry}'"
276 ),
277 Error::InvalidAttributeValue { attribute, value } => {
278 write!(f, "invalid value '{value}' for attribute '{attribute}'")
279 }
280 Error::InvalidTimezoneOffset { seconds } => write!(
281 f,
282 "timezone offset of {seconds} s is invalid; \
283 XSD requires whole minutes within -14:00..=+14:00"
284 ),
285 Error::DateTimeOutOfRange { value } => {
286 write!(f, "date-time '{value}' is outside the supported range")
287 }
288 Error::InvalidDateTimeFields { value } => {
289 write!(f, "fields {value} do not form a valid date or time")
290 }
291 }
292 }
293}
294
295impl std::error::Error for Error {}