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 ring 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    UnresolvedRingReference { href: Option<String> },
117
118    /// Returned when a surface 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    UnresolvedSurfaceReference { href: Option<String> },
124
125    /// Returned when a curve property carries only an xlink:href reference and the
126    /// referenced geometry object has not been resolved into an inline object.
127    ///
128    /// `href` is the reference value if one was present, or `None` if the property
129    /// has neither an inline object nor a reference.
130    UnresolvedCurveReference { href: Option<String> },
131
132    /// Returned when an operation requires an exterior shell but the solid has none.
133    MissingExteriorShell,
134
135    /// Returned when a shell property carries only an xlink:href reference and the
136    /// referenced geometry object has not been resolved into an inline object.
137    ///
138    /// `href` is the reference value if one was present, or `None` if the property
139    /// has neither an inline object nor a reference.
140    UnresolvedShellReference { href: Option<String> },
141
142    /// Returned when `triangulate` is called on a geometry type that cannot
143    /// produce a surface (e.g. `Point`, `MultiCurve`).
144    ///
145    /// `geometry` names the concrete variant that was encountered.
146    TriangulationNotSupported { geometry: &'static str },
147
148    /// Returned when an attribute held a value that could not be parsed into
149    /// its expected type (e.g. an `xlink:show`/`xlink:actuate` value outside
150    /// the enumeration defined by the XLink specification).
151    ///
152    /// `attribute` names the attribute (e.g. `"xlink:show"`); `value` is the
153    /// raw string that was supplied.
154    InvalidAttributeValue {
155        attribute: &'static str,
156        value: String,
157    },
158}
159
160impl fmt::Display for Error {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        match self {
163            Error::NonFiniteCoordinate { axis, value } => write!(
164                f,
165                "coordinate '{axis}' has non-finite value {value}; \
166                 all GML coordinate values must be real numbers (OGC 07-036 §10.1.4.1)"
167            ),
168            Error::TooFewElements {
169                geometry,
170                minimum,
171                spec,
172                id,
173                detail,
174            } => {
175                write!(f, "{geometry} requires at least {minimum} element(s)")?;
176                if let Some(s) = spec {
177                    write!(f, " ({s})")?;
178                }
179                if let Some(i) = id {
180                    write!(f, " [id={i}]")?;
181                }
182                if let Some(d) = detail {
183                    write!(f, ": {d}")?;
184                }
185                Ok(())
186            }
187            Error::InvalidElementCount {
188                geometry,
189                expected,
190                actual,
191                spec,
192            } => {
193                write!(
194                    f,
195                    "{geometry} requires exactly {expected} element(s), got {actual}"
196                )?;
197                if let Some(s) = spec {
198                    write!(f, " ({s})")?;
199                }
200                Ok(())
201            }
202            Error::IdenticalPositions { first, second } => write!(
203                f,
204                "geometry contains two identical positions {first} and {second}; \
205                 all positions must be distinct (OGC 07-036 §10.5.12.5)"
206            ),
207            Error::AdjacentDuplicatePositions { index, position } => write!(
208                f,
209                "adjacent duplicate positions at index {index} ({position}); \
210                 consecutive coordinates must be distinct"
211            ),
212            Error::RepeatedClosingVertex { position } => write!(
213                f,
214                "linear ring has repeated closing vertex at {position}; \
215                 gml:LinearRing is implicitly closed and must not include \
216                 an explicit closing vertex (OGC 07-036 §10.5.8)"
217            ),
218            Error::InvalidEnvelopeBounds { axis, lower, upper } => write!(
219                f,
220                "envelope lower.{axis}={lower} exceeds upper.{axis}={upper}; \
221                 lowerCorner must be ≤ upperCorner in every component (OGC 07-036 §10.1.4.6)"
222            ),
223            Error::NotSurfaceOrVolume { non_zero_extents } => write!(
224                f,
225                "envelope has {non_zero_extents} non-zero extent(s) and cannot be triangulated; \
226                 requires a surface (2 non-zero extents) or volume (3 non-zero extents)"
227            ),
228            Error::NotASurface { non_zero_extents } => write!(
229                f,
230                "envelope has {non_zero_extents} non-zero extent(s); \
231                 to_polygon requires exactly 2"
232            ),
233            Error::NotAVolume { non_zero_extents } => write!(
234                f,
235                "envelope has {non_zero_extents} non-zero extent(s); \
236                 to_solid requires all 3 to be non-zero"
237            ),
238            Error::EmptyId => write!(f, "gml:id must not be empty"),
239            Error::TriangulationFailed { context } => {
240                write!(f, "polygon triangulation (earcut) failed: {context}")
241            }
242            Error::MissingExteriorRing => write!(
243                f,
244                "polygon has no exterior ring; \
245                 operation requires a defined outer boundary (OGC 07-036 §10.5.6)"
246            ),
247            Error::UnresolvedRingReference { href: Some(href) } => write!(
248                f,
249                "ring property references '{href}' via xlink:href but the object has not been resolved"
250            ),
251            Error::UnresolvedRingReference { href: None } => write!(
252                f,
253                "ring property has neither an inline object nor an xlink:href reference"
254            ),
255            Error::UnresolvedSurfaceReference { href: Some(href) } => write!(
256                f,
257                "surface property references '{href}' via xlink:href but the object has not been resolved"
258            ),
259            Error::UnresolvedSurfaceReference { href: None } => write!(
260                f,
261                "surface property has neither an inline object nor an xlink:href reference"
262            ),
263            Error::UnresolvedCurveReference { href: Some(href) } => write!(
264                f,
265                "curve property references '{href}' via xlink:href but the object has not been resolved"
266            ),
267            Error::UnresolvedCurveReference { href: None } => write!(
268                f,
269                "curve property has neither an inline object nor an xlink:href reference"
270            ),
271            Error::MissingExteriorShell => write!(
272                f,
273                "solid has no exterior shell; \
274                 operation requires a defined outer boundary (OGC 07-036 §10.6.4)"
275            ),
276            Error::UnresolvedShellReference { href: Some(href) } => write!(
277                f,
278                "shell property references '{href}' via xlink:href but the object has not been resolved"
279            ),
280            Error::UnresolvedShellReference { href: None } => write!(
281                f,
282                "shell property has neither an inline object nor an xlink:href reference"
283            ),
284            Error::TriangulationNotSupported { geometry } => write!(
285                f,
286                "triangulation is not supported for geometry type '{geometry}'"
287            ),
288            Error::InvalidAttributeValue { attribute, value } => {
289                write!(f, "invalid value '{value}' for attribute '{attribute}'")
290            }
291        }
292    }
293}
294
295impl std::error::Error for Error {}