Skip to main content

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 {}