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 two positions that must be distinct are identical.
30    ///
31    /// Applies to [`Triangle`](crate::model::geometry::primitives::Triangle)
32    /// vertices ([OGC 07-036 §10.5.12.5](https://docs.ogc.org/is/07-036/07-036.pdf)).
33    IdenticalPositions {
34        first: DirectPosition,
35        second: DirectPosition,
36    },
37
38    /// Returned when adjacent positions in a sequence are equal.
39    ///
40    /// `index` is the zero-based index of the first position in the duplicate
41    /// pair; `position` is its value. Applies to
42    /// [`LinearRing`](crate::model::geometry::primitives::LinearRing)
43    /// ([OGC 07-036 §10.5.8](https://docs.ogc.org/is/07-036/07-036.pdf)) and
44    /// [`LineString`](crate::model::geometry::primitives::LineString)
45    /// ([OGC 07-036 §10.4.4](https://docs.ogc.org/is/07-036/07-036.pdf)).
46    AdjacentDuplicatePositions {
47        index: usize,
48        position: DirectPosition,
49    },
50
51    /// Returned when the first and last position of a ring are equal.
52    ///
53    /// `position` is the repeated vertex. A `gml:LinearRing` is implicitly
54    /// 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)).
55    RepeatedClosingVertex { position: DirectPosition },
56
57    /// Returned when an [`Envelope`](crate::model::geometry::Envelope) is constructed
58    /// with a lower corner that strictly exceeds the upper corner along `axis`.
59    ///
60    /// `lower` and `upper` are the actual conflicting coordinate values.
61    /// [OGC 07-036 §10.1.4.6](https://docs.ogc.org/is/07-036/07-036.pdf) requires `lowerCorner ≤ upperCorner` in every component.
62    InvalidEnvelopeBounds {
63        axis: &'static str,
64        lower: f64,
65        upper: f64,
66    },
67
68    /// Returned when
69    /// [`Envelope::to_triangulated_surface`](crate::model::geometry::Envelope::to_triangulated_surface)
70    /// is called on an envelope that is neither a surface nor a volume.
71    ///
72    /// `non_zero_extents` is the number of axes with non-zero extent (0 or 1).
73    NotSurfaceOrVolume { non_zero_extents: u8 },
74
75    /// Returned when [`Envelope::to_polygon`](crate::model::geometry::Envelope::to_polygon)
76    /// is called on an envelope that does not have exactly two non-zero extents.
77    ///
78    /// `non_zero_extents` is the actual number of axes with non-zero extent.
79    NotASurface { non_zero_extents: u8 },
80
81    /// Returned when [`Envelope::to_solid`](crate::model::geometry::Envelope::to_solid)
82    /// is called on an envelope that does not have all three extents non-zero.
83    ///
84    /// `non_zero_extents` is the actual number of axes with non-zero extent.
85    NotAVolume { non_zero_extents: u8 },
86
87    /// Returned when the earcut polygon triangulation algorithm produces no triangles.
88    ///
89    /// `context` provides additional information about which polygon or patch
90    /// could not be decomposed.
91    TriangulationFailed { context: String },
92
93    /// Returned when an operation requires an exterior ring but the polygon has none.
94    MissingExteriorRing,
95
96    /// Returned when a ring property carries only an xlink:href reference and the
97    /// referenced geometry object has not been resolved into an inline object.
98    ///
99    /// `href` is the reference value if one was present, or `None` if the property
100    /// has neither an inline object nor a reference.
101    UnresolvedRingReference { href: Option<String> },
102
103    /// Returned when a surface property carries only an xlink:href reference and the
104    /// referenced geometry object has not been resolved into an inline object.
105    ///
106    /// `href` is the reference value if one was present, or `None` if the property
107    /// has neither an inline object nor a reference.
108    UnresolvedSurfaceReference { href: Option<String> },
109
110    /// Returned when a curve property carries only an xlink:href reference and the
111    /// referenced geometry object has not been resolved into an inline object.
112    ///
113    /// `href` is the reference value if one was present, or `None` if the property
114    /// has neither an inline object nor a reference.
115    UnresolvedCurveReference { href: Option<String> },
116
117    /// Returned when an operation requires an exterior shell but the solid has none.
118    MissingExteriorShell,
119
120    /// Returned when a shell property carries only an xlink:href reference and the
121    /// referenced geometry object has not been resolved into an inline object.
122    ///
123    /// `href` is the reference value if one was present, or `None` if the property
124    /// has neither an inline object nor a reference.
125    UnresolvedShellReference { href: Option<String> },
126}
127
128impl fmt::Display for Error {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        match self {
131            Error::NonFiniteCoordinate { axis, value } => write!(
132                f,
133                "coordinate '{axis}' has non-finite value {value}; \
134                 all GML coordinate values must be real numbers (OGC 07-036 §10.1.4.1)"
135            ),
136            Error::TooFewElements {
137                geometry,
138                minimum,
139                spec,
140                id,
141                detail,
142            } => {
143                write!(f, "{geometry} requires at least {minimum} element(s)")?;
144                if let Some(s) = spec {
145                    write!(f, " ({s})")?;
146                }
147                if let Some(i) = id {
148                    write!(f, " [id={i}]")?;
149                }
150                if let Some(d) = detail {
151                    write!(f, ": {d}")?;
152                }
153                Ok(())
154            }
155            Error::IdenticalPositions { first, second } => write!(
156                f,
157                "geometry contains two identical positions {first} and {second}; \
158                 all positions must be distinct (OGC 07-036 §10.5.12.5)"
159            ),
160            Error::AdjacentDuplicatePositions { index, position } => write!(
161                f,
162                "adjacent duplicate positions at index {index} ({position}); \
163                 consecutive coordinates must be distinct"
164            ),
165            Error::RepeatedClosingVertex { position } => write!(
166                f,
167                "linear ring has repeated closing vertex at {position}; \
168                 gml:LinearRing is implicitly closed and must not include \
169                 an explicit closing vertex (OGC 07-036 §10.5.8)"
170            ),
171            Error::InvalidEnvelopeBounds { axis, lower, upper } => write!(
172                f,
173                "envelope lower.{axis}={lower} exceeds upper.{axis}={upper}; \
174                 lowerCorner must be ≤ upperCorner in every component (OGC 07-036 §10.1.4.6)"
175            ),
176            Error::NotSurfaceOrVolume { non_zero_extents } => write!(
177                f,
178                "envelope has {non_zero_extents} non-zero extent(s) and cannot be triangulated; \
179                 requires a surface (2 non-zero extents) or volume (3 non-zero extents)"
180            ),
181            Error::NotASurface { non_zero_extents } => write!(
182                f,
183                "envelope has {non_zero_extents} non-zero extent(s); \
184                 to_polygon requires exactly 2"
185            ),
186            Error::NotAVolume { non_zero_extents } => write!(
187                f,
188                "envelope has {non_zero_extents} non-zero extent(s); \
189                 to_solid requires all 3 to be non-zero"
190            ),
191            Error::TriangulationFailed { context } => {
192                write!(f, "polygon triangulation (earcut) failed: {context}")
193            }
194            Error::MissingExteriorRing => write!(
195                f,
196                "polygon has no exterior ring; \
197                 operation requires a defined outer boundary (OGC 07-036 §10.5.6)"
198            ),
199            Error::UnresolvedRingReference { href: Some(href) } => write!(
200                f,
201                "ring property references '{href}' via xlink:href but the object has not been resolved"
202            ),
203            Error::UnresolvedRingReference { href: None } => write!(
204                f,
205                "ring property has neither an inline object nor an xlink:href reference"
206            ),
207            Error::UnresolvedSurfaceReference { href: Some(href) } => write!(
208                f,
209                "surface property references '{href}' via xlink:href but the object has not been resolved"
210            ),
211            Error::UnresolvedSurfaceReference { href: None } => write!(
212                f,
213                "surface property has neither an inline object nor an xlink:href reference"
214            ),
215            Error::UnresolvedCurveReference { href: Some(href) } => write!(
216                f,
217                "curve property references '{href}' via xlink:href but the object has not been resolved"
218            ),
219            Error::UnresolvedCurveReference { href: None } => write!(
220                f,
221                "curve property has neither an inline object nor an xlink:href reference"
222            ),
223            Error::MissingExteriorShell => write!(
224                f,
225                "solid has no exterior shell; \
226                 operation requires a defined outer boundary (OGC 07-036 §10.6.4)"
227            ),
228            Error::UnresolvedShellReference { href: Some(href) } => write!(
229                f,
230                "shell property references '{href}' via xlink:href but the object has not been resolved"
231            ),
232            Error::UnresolvedShellReference { href: None } => write!(
233                f,
234                "shell property has neither an inline object nor an xlink:href reference"
235            ),
236        }
237    }
238}
239
240impl std::error::Error for Error {}