Skip to main content

oxigdal_proj/
error.rs

1//! Error types for projection and coordinate transformation operations.
2//!
3//! This module provides comprehensive error handling for all projection-related operations,
4//! following the no-unwrap policy.
5
6#[cfg(not(feature = "std"))]
7use alloc::string::String;
8
9/// Result type for projection operations.
10pub type Result<T> = core::result::Result<T, Error>;
11
12/// Comprehensive error type for projection operations.
13#[derive(Debug, thiserror::Error)]
14pub enum Error {
15    /// Invalid EPSG code
16    #[error("Invalid EPSG code: {code}")]
17    InvalidEpsgCode {
18        /// The invalid EPSG code
19        code: u32,
20    },
21
22    /// EPSG code not found in database
23    #[error("EPSG code {code} not found in database")]
24    EpsgCodeNotFound {
25        /// The EPSG code that was not found
26        code: u32,
27    },
28
29    /// Invalid PROJ string
30    #[error("Invalid PROJ string: {reason}")]
31    InvalidProjString {
32        /// Reason for the invalid PROJ string
33        reason: String,
34    },
35
36    /// Invalid WKT (Well-Known Text) string
37    #[error("Invalid WKT string: {reason}")]
38    InvalidWkt {
39        /// Reason for the invalid WKT
40        reason: String,
41    },
42
43    /// WKT parsing error
44    #[error("WKT parsing error at position {position}: {message}")]
45    WktParseError {
46        /// Position in the WKT string where error occurred
47        position: usize,
48        /// Error message
49        message: String,
50    },
51
52    /// Coordinate transformation error
53    #[error("Coordinate transformation failed: {reason}")]
54    TransformationError {
55        /// Reason for transformation failure
56        reason: String,
57    },
58
59    /// Unsupported CRS (Coordinate Reference System)
60    #[error("Unsupported CRS: {crs_type}")]
61    UnsupportedCrs {
62        /// Type of CRS that is not supported
63        crs_type: String,
64    },
65
66    /// Incompatible source and target CRS
67    #[error("Incompatible CRS for transformation: source={src}, target={tgt}")]
68    IncompatibleCrs {
69        /// Source CRS description
70        src: String,
71        /// Target CRS description
72        tgt: String,
73    },
74
75    /// Invalid coordinate
76    #[error("Invalid coordinate: {reason}")]
77    InvalidCoordinate {
78        /// Reason for invalid coordinate
79        reason: String,
80    },
81
82    /// Out of bounds coordinate
83    #[error("Coordinate out of valid bounds: ({x}, {y})")]
84    CoordinateOutOfBounds {
85        /// X coordinate
86        x: f64,
87        /// Y coordinate
88        y: f64,
89    },
90
91    /// Invalid bounding box
92    #[error("Invalid bounding box: {reason}")]
93    InvalidBoundingBox {
94        /// Reason for invalid bounding box
95        reason: String,
96    },
97
98    /// Missing required parameter
99    #[error("Missing required parameter: {parameter}")]
100    MissingParameter {
101        /// Name of missing parameter
102        parameter: String,
103    },
104
105    /// Invalid parameter value
106    #[error("Invalid parameter value for {parameter}: {reason}")]
107    InvalidParameter {
108        /// Parameter name
109        parameter: String,
110        /// Reason for invalid value
111        reason: String,
112    },
113
114    /// Datum transformation error
115    #[error("Datum transformation failed: {reason}")]
116    DatumTransformError {
117        /// Reason for datum transformation failure
118        reason: String,
119    },
120
121    /// Projection initialization error
122    #[error("Failed to initialize projection: {reason}")]
123    ProjectionInitError {
124        /// Reason for initialization failure
125        reason: String,
126    },
127
128    /// Unsupported projection
129    #[error("Unsupported projection: {projection}")]
130    UnsupportedProjection {
131        /// Name of unsupported projection
132        projection: String,
133    },
134
135    /// Numerical error (e.g., division by zero, sqrt of negative)
136    #[error("Numerical error in projection calculation: {operation}")]
137    NumericalError {
138        /// Operation that caused the error
139        operation: String,
140    },
141
142    /// Convergence failure in iterative algorithms
143    #[error("Failed to converge after {iterations} iterations")]
144    ConvergenceError {
145        /// Number of iterations attempted
146        iterations: usize,
147    },
148
149    /// JSON serialization/deserialization error
150    #[cfg(feature = "std")]
151    #[error("JSON error: {0}")]
152    JsonError(#[from] serde_json::Error),
153
154    /// I/O error
155    #[cfg(feature = "std")]
156    #[error("I/O error: {0}")]
157    IoError(#[from] std::io::Error),
158
159    /// UTF-8 conversion error
160    #[cfg(feature = "std")]
161    #[error("UTF-8 conversion error: {0}")]
162    Utf8Error(#[from] std::str::Utf8Error),
163
164    /// Error from proj4rs library
165    #[cfg(feature = "std")]
166    #[error("Proj4rs error: {0}")]
167    Proj4rsError(String),
168
169    /// Error from PROJ C library (when using proj-sys feature)
170    #[cfg(feature = "proj-sys")]
171    #[error("PROJ library error: {0}")]
172    ProjSysError(String),
173
174    /// Generic error for cases not covered by specific error types
175    #[error("{0}")]
176    Other(String),
177}
178
179impl Error {
180    /// Creates an invalid EPSG code error.
181    pub fn invalid_epsg_code(code: u32) -> Self {
182        Self::InvalidEpsgCode { code }
183    }
184
185    /// Creates an EPSG code not found error.
186    pub fn epsg_not_found(code: u32) -> Self {
187        Self::EpsgCodeNotFound { code }
188    }
189
190    /// Creates an invalid PROJ string error.
191    pub fn invalid_proj_string<S: Into<String>>(reason: S) -> Self {
192        Self::InvalidProjString {
193            reason: reason.into(),
194        }
195    }
196
197    /// Creates an invalid WKT error.
198    pub fn invalid_wkt<S: Into<String>>(reason: S) -> Self {
199        Self::InvalidWkt {
200            reason: reason.into(),
201        }
202    }
203
204    /// Creates a WKT parsing error.
205    pub fn wkt_parse_error<S: Into<String>>(position: usize, message: S) -> Self {
206        Self::WktParseError {
207            position,
208            message: message.into(),
209        }
210    }
211
212    /// Creates a transformation error.
213    pub fn transformation_error<S: Into<String>>(reason: S) -> Self {
214        Self::TransformationError {
215            reason: reason.into(),
216        }
217    }
218
219    /// Creates an unsupported CRS error.
220    pub fn unsupported_crs<S: Into<String>>(crs_type: S) -> Self {
221        Self::UnsupportedCrs {
222            crs_type: crs_type.into(),
223        }
224    }
225
226    /// Creates an incompatible CRS error.
227    pub fn incompatible_crs<S: Into<String>>(src: S, tgt: S) -> Self {
228        Self::IncompatibleCrs {
229            src: src.into(),
230            tgt: tgt.into(),
231        }
232    }
233
234    /// Creates an invalid coordinate error.
235    pub fn invalid_coordinate<S: Into<String>>(reason: S) -> Self {
236        Self::InvalidCoordinate {
237            reason: reason.into(),
238        }
239    }
240
241    /// Creates a coordinate out of bounds error.
242    pub fn coordinate_out_of_bounds(x: f64, y: f64) -> Self {
243        Self::CoordinateOutOfBounds { x, y }
244    }
245
246    /// Creates an invalid bounding box error.
247    pub fn invalid_bounding_box<S: Into<String>>(reason: S) -> Self {
248        Self::InvalidBoundingBox {
249            reason: reason.into(),
250        }
251    }
252
253    /// Creates a missing parameter error.
254    pub fn missing_parameter<S: Into<String>>(parameter: S) -> Self {
255        Self::MissingParameter {
256            parameter: parameter.into(),
257        }
258    }
259
260    /// Creates an invalid parameter error.
261    pub fn invalid_parameter<S: Into<String>>(parameter: S, reason: S) -> Self {
262        Self::InvalidParameter {
263            parameter: parameter.into(),
264            reason: reason.into(),
265        }
266    }
267
268    /// Creates a datum transform error.
269    pub fn datum_transform_error<S: Into<String>>(reason: S) -> Self {
270        Self::DatumTransformError {
271            reason: reason.into(),
272        }
273    }
274
275    /// Creates a projection initialization error.
276    pub fn projection_init_error<S: Into<String>>(reason: S) -> Self {
277        Self::ProjectionInitError {
278            reason: reason.into(),
279        }
280    }
281
282    /// Creates an unsupported projection error.
283    pub fn unsupported_projection<S: Into<String>>(projection: S) -> Self {
284        Self::UnsupportedProjection {
285            projection: projection.into(),
286        }
287    }
288
289    /// Creates a numerical error.
290    pub fn numerical_error<S: Into<String>>(operation: S) -> Self {
291        Self::NumericalError {
292            operation: operation.into(),
293        }
294    }
295
296    /// Creates a convergence error.
297    pub fn convergence_error(iterations: usize) -> Self {
298        Self::ConvergenceError { iterations }
299    }
300
301    /// Creates an error from proj4rs library.
302    #[cfg(feature = "std")]
303    pub fn from_proj4rs<S: Into<String>>(message: S) -> Self {
304        Self::Proj4rsError(message.into())
305    }
306
307    /// Creates a generic other error.
308    pub fn other<S: Into<String>>(message: S) -> Self {
309        Self::Other(message.into())
310    }
311}
312
313// Implement conversion from proj4rs errors
314#[cfg(feature = "std")]
315impl From<proj4rs::errors::Error> for Error {
316    fn from(err: proj4rs::errors::Error) -> Self {
317        Self::from_proj4rs(format!("{:?}", err))
318    }
319}
320
321#[cfg(feature = "proj-sys")]
322impl From<proj::ProjError> for Error {
323    fn from(err: proj::ProjError) -> Self {
324        Self::ProjSysError(format!("{}", err))
325    }
326}
327
328#[cfg(test)]
329#[allow(clippy::expect_used)]
330mod tests {
331    use super::*;
332
333    #[test]
334    fn test_error_creation() {
335        let err = Error::invalid_epsg_code(12345);
336        assert!(matches!(err, Error::InvalidEpsgCode { code: 12345 }));
337
338        let err = Error::epsg_not_found(4326);
339        assert!(matches!(err, Error::EpsgCodeNotFound { code: 4326 }));
340
341        let err = Error::invalid_proj_string("missing parameter");
342        assert!(matches!(err, Error::InvalidProjString { .. }));
343
344        let err = Error::transformation_error("invalid coordinates");
345        assert!(matches!(err, Error::TransformationError { .. }));
346    }
347
348    #[test]
349    fn test_error_display() {
350        let err = Error::invalid_epsg_code(12345);
351        assert_eq!(format!("{}", err), "Invalid EPSG code: 12345");
352
353        let err = Error::coordinate_out_of_bounds(180.5, 90.5);
354        assert_eq!(
355            format!("{}", err),
356            "Coordinate out of valid bounds: (180.5, 90.5)"
357        );
358    }
359
360    #[test]
361    fn test_result_type() {
362        fn returns_ok() -> Result<i32> {
363            Ok(42)
364        }
365
366        fn returns_error() -> Result<i32> {
367            Err(Error::invalid_epsg_code(0))
368        }
369
370        assert!(returns_ok().is_ok());
371        assert!(returns_error().is_err());
372    }
373}