1#[cfg(not(feature = "std"))]
7use alloc::string::String;
8
9pub type Result<T> = core::result::Result<T, Error>;
11
12#[derive(Debug, thiserror::Error)]
14pub enum Error {
15 #[error("Invalid EPSG code: {code}")]
17 InvalidEpsgCode {
18 code: u32,
20 },
21
22 #[error("EPSG code {code} not found in database")]
24 EpsgCodeNotFound {
25 code: u32,
27 },
28
29 #[error("Invalid PROJ string: {reason}")]
31 InvalidProjString {
32 reason: String,
34 },
35
36 #[error("Invalid WKT string: {reason}")]
38 InvalidWkt {
39 reason: String,
41 },
42
43 #[error("WKT parsing error at position {position}: {message}")]
45 WktParseError {
46 position: usize,
48 message: String,
50 },
51
52 #[error("Coordinate transformation failed: {reason}")]
54 TransformationError {
55 reason: String,
57 },
58
59 #[error("Unsupported CRS: {crs_type}")]
61 UnsupportedCrs {
62 crs_type: String,
64 },
65
66 #[error("Incompatible CRS for transformation: source={src}, target={tgt}")]
68 IncompatibleCrs {
69 src: String,
71 tgt: String,
73 },
74
75 #[error("Invalid coordinate: {reason}")]
77 InvalidCoordinate {
78 reason: String,
80 },
81
82 #[error("Coordinate out of valid bounds: ({x}, {y})")]
84 CoordinateOutOfBounds {
85 x: f64,
87 y: f64,
89 },
90
91 #[error("Invalid bounding box: {reason}")]
93 InvalidBoundingBox {
94 reason: String,
96 },
97
98 #[error("Missing required parameter: {parameter}")]
100 MissingParameter {
101 parameter: String,
103 },
104
105 #[error("Invalid parameter value for {parameter}: {reason}")]
107 InvalidParameter {
108 parameter: String,
110 reason: String,
112 },
113
114 #[error("Datum transformation failed: {reason}")]
116 DatumTransformError {
117 reason: String,
119 },
120
121 #[error("Failed to initialize projection: {reason}")]
123 ProjectionInitError {
124 reason: String,
126 },
127
128 #[error("Unsupported projection: {projection}")]
130 UnsupportedProjection {
131 projection: String,
133 },
134
135 #[error("Numerical error in projection calculation: {operation}")]
137 NumericalError {
138 operation: String,
140 },
141
142 #[error("Failed to converge after {iterations} iterations")]
144 ConvergenceError {
145 iterations: usize,
147 },
148
149 #[cfg(feature = "std")]
151 #[error("JSON error: {0}")]
152 JsonError(#[from] serde_json::Error),
153
154 #[cfg(feature = "std")]
156 #[error("I/O error: {0}")]
157 IoError(#[from] std::io::Error),
158
159 #[cfg(feature = "std")]
161 #[error("UTF-8 conversion error: {0}")]
162 Utf8Error(#[from] std::str::Utf8Error),
163
164 #[cfg(feature = "std")]
166 #[error("Proj4rs error: {0}")]
167 Proj4rsError(String),
168
169 #[cfg(feature = "proj-sys")]
171 #[error("PROJ library error: {0}")]
172 ProjSysError(String),
173
174 #[error("Coordinate ({lon}, {lat}) is outside area of use for CRS '{crs}'")]
176 OutOfAreaOfUse {
177 lon: f64,
179 lat: f64,
181 crs: String,
183 },
184
185 #[error(
189 "coordinate ({lon}, {lat}) outside area-of-use for EPSG:{epsg} \
190 (west={west}, south={south}, east={east}, north={north})"
191 )]
192 OutsideAreaOfUse {
193 lon: f64,
195 lat: f64,
197 epsg: u32,
199 west: f64,
201 south: f64,
203 east: f64,
205 north: f64,
207 },
208
209 #[error("NTv2 parse error: {0}")]
211 Ntv2ParseError(String),
212
213 #[error("Coordinate ({lon}, {lat}) is outside NTv2 grid extent")]
215 Ntv2OutOfGrid {
216 lon: f64,
218 lat: f64,
220 },
221
222 #[error("Invalid compound CRS: {reason}")]
224 InvalidCompoundCrs {
225 reason: String,
227 },
228
229 #[error("Geoid model not available for vertical CRS: {vertical_crs}")]
231 GeoidNotAvailable {
232 vertical_crs: String,
234 },
235
236 #[error("geoid file format: {0}")]
238 GeoidFileFormat(String),
239
240 #[error("no geoid model available for compound CRS transform")]
244 NoGeoidAvailable,
245
246 #[error("PROJ.db error: {0}")]
248 ProjDbError(String),
249
250 #[error("{0}")]
252 Other(String),
253
254 #[error("Pipeline parse error: {0}")]
256 PipelineParseError(String),
257
258 #[error("Pipeline step {step} failed: {inner}")]
260 PipelineStepError {
261 step: usize,
263 inner: String,
265 },
266}
267
268impl Error {
269 pub fn invalid_epsg_code(code: u32) -> Self {
271 Self::InvalidEpsgCode { code }
272 }
273
274 pub fn epsg_not_found(code: u32) -> Self {
276 Self::EpsgCodeNotFound { code }
277 }
278
279 pub fn invalid_proj_string<S: Into<String>>(reason: S) -> Self {
281 Self::InvalidProjString {
282 reason: reason.into(),
283 }
284 }
285
286 pub fn invalid_wkt<S: Into<String>>(reason: S) -> Self {
288 Self::InvalidWkt {
289 reason: reason.into(),
290 }
291 }
292
293 pub fn wkt_parse_error<S: Into<String>>(position: usize, message: S) -> Self {
295 Self::WktParseError {
296 position,
297 message: message.into(),
298 }
299 }
300
301 pub fn transformation_error<S: Into<String>>(reason: S) -> Self {
303 Self::TransformationError {
304 reason: reason.into(),
305 }
306 }
307
308 pub fn unsupported_crs<S: Into<String>>(crs_type: S) -> Self {
310 Self::UnsupportedCrs {
311 crs_type: crs_type.into(),
312 }
313 }
314
315 pub fn incompatible_crs<S: Into<String>>(src: S, tgt: S) -> Self {
317 Self::IncompatibleCrs {
318 src: src.into(),
319 tgt: tgt.into(),
320 }
321 }
322
323 pub fn invalid_coordinate<S: Into<String>>(reason: S) -> Self {
325 Self::InvalidCoordinate {
326 reason: reason.into(),
327 }
328 }
329
330 pub fn coordinate_out_of_bounds(x: f64, y: f64) -> Self {
332 Self::CoordinateOutOfBounds { x, y }
333 }
334
335 pub fn invalid_bounding_box<S: Into<String>>(reason: S) -> Self {
337 Self::InvalidBoundingBox {
338 reason: reason.into(),
339 }
340 }
341
342 pub fn missing_parameter<S: Into<String>>(parameter: S) -> Self {
344 Self::MissingParameter {
345 parameter: parameter.into(),
346 }
347 }
348
349 pub fn invalid_parameter<S: Into<String>>(parameter: S, reason: S) -> Self {
351 Self::InvalidParameter {
352 parameter: parameter.into(),
353 reason: reason.into(),
354 }
355 }
356
357 pub fn datum_transform_error<S: Into<String>>(reason: S) -> Self {
359 Self::DatumTransformError {
360 reason: reason.into(),
361 }
362 }
363
364 pub fn projection_init_error<S: Into<String>>(reason: S) -> Self {
366 Self::ProjectionInitError {
367 reason: reason.into(),
368 }
369 }
370
371 pub fn unsupported_projection<S: Into<String>>(projection: S) -> Self {
373 Self::UnsupportedProjection {
374 projection: projection.into(),
375 }
376 }
377
378 pub fn numerical_error<S: Into<String>>(operation: S) -> Self {
380 Self::NumericalError {
381 operation: operation.into(),
382 }
383 }
384
385 pub fn convergence_error(iterations: usize) -> Self {
387 Self::ConvergenceError { iterations }
388 }
389
390 #[cfg(feature = "std")]
392 pub fn from_proj4rs<S: Into<String>>(message: S) -> Self {
393 Self::Proj4rsError(message.into())
394 }
395
396 pub fn out_of_area_of_use<S: Into<String>>(lon: f64, lat: f64, crs: S) -> Self {
398 Self::OutOfAreaOfUse {
399 lon,
400 lat,
401 crs: crs.into(),
402 }
403 }
404
405 pub fn ntv2_parse_error<S: Into<String>>(message: S) -> Self {
407 Self::Ntv2ParseError(message.into())
408 }
409
410 pub fn ntv2_out_of_grid(lon: f64, lat: f64) -> Self {
412 Self::Ntv2OutOfGrid { lon, lat }
413 }
414
415 pub fn invalid_compound_crs(reason: impl Into<String>) -> Self {
417 Self::InvalidCompoundCrs {
418 reason: reason.into(),
419 }
420 }
421
422 pub fn geoid_not_available(vertical_crs: impl Into<String>) -> Self {
424 Self::GeoidNotAvailable {
425 vertical_crs: vertical_crs.into(),
426 }
427 }
428
429 pub fn other<S: Into<String>>(message: S) -> Self {
431 Self::Other(message.into())
432 }
433}
434
435#[cfg(feature = "std")]
437impl From<proj4rs::errors::Error> for Error {
438 fn from(err: proj4rs::errors::Error) -> Self {
439 Self::from_proj4rs(format!("{:?}", err))
440 }
441}
442
443#[cfg(feature = "proj-sys")]
444impl From<proj::ProjError> for Error {
445 fn from(err: proj::ProjError) -> Self {
446 Self::ProjSysError(format!("{}", err))
447 }
448}
449
450#[cfg(test)]
451#[allow(clippy::expect_used)]
452mod tests {
453 use super::*;
454
455 #[test]
456 fn test_error_creation() {
457 let err = Error::invalid_epsg_code(12345);
458 assert!(matches!(err, Error::InvalidEpsgCode { code: 12345 }));
459
460 let err = Error::epsg_not_found(4326);
461 assert!(matches!(err, Error::EpsgCodeNotFound { code: 4326 }));
462
463 let err = Error::invalid_proj_string("missing parameter");
464 assert!(matches!(err, Error::InvalidProjString { .. }));
465
466 let err = Error::transformation_error("invalid coordinates");
467 assert!(matches!(err, Error::TransformationError { .. }));
468 }
469
470 #[test]
471 fn test_error_display() {
472 let err = Error::invalid_epsg_code(12345);
473 assert_eq!(format!("{}", err), "Invalid EPSG code: 12345");
474
475 let err = Error::coordinate_out_of_bounds(180.5, 90.5);
476 assert_eq!(
477 format!("{}", err),
478 "Coordinate out of valid bounds: (180.5, 90.5)"
479 );
480 }
481
482 #[test]
483 fn test_result_type() {
484 fn returns_ok() -> Result<i32> {
485 Ok(42)
486 }
487
488 fn returns_error() -> Result<i32> {
489 Err(Error::invalid_epsg_code(0))
490 }
491
492 assert!(returns_ok().is_ok());
493 assert!(returns_error().is_err());
494 }
495}