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("{0}")]
176 Other(String),
177}
178
179impl Error {
180 pub fn invalid_epsg_code(code: u32) -> Self {
182 Self::InvalidEpsgCode { code }
183 }
184
185 pub fn epsg_not_found(code: u32) -> Self {
187 Self::EpsgCodeNotFound { code }
188 }
189
190 pub fn invalid_proj_string<S: Into<String>>(reason: S) -> Self {
192 Self::InvalidProjString {
193 reason: reason.into(),
194 }
195 }
196
197 pub fn invalid_wkt<S: Into<String>>(reason: S) -> Self {
199 Self::InvalidWkt {
200 reason: reason.into(),
201 }
202 }
203
204 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 pub fn transformation_error<S: Into<String>>(reason: S) -> Self {
214 Self::TransformationError {
215 reason: reason.into(),
216 }
217 }
218
219 pub fn unsupported_crs<S: Into<String>>(crs_type: S) -> Self {
221 Self::UnsupportedCrs {
222 crs_type: crs_type.into(),
223 }
224 }
225
226 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 pub fn invalid_coordinate<S: Into<String>>(reason: S) -> Self {
236 Self::InvalidCoordinate {
237 reason: reason.into(),
238 }
239 }
240
241 pub fn coordinate_out_of_bounds(x: f64, y: f64) -> Self {
243 Self::CoordinateOutOfBounds { x, y }
244 }
245
246 pub fn invalid_bounding_box<S: Into<String>>(reason: S) -> Self {
248 Self::InvalidBoundingBox {
249 reason: reason.into(),
250 }
251 }
252
253 pub fn missing_parameter<S: Into<String>>(parameter: S) -> Self {
255 Self::MissingParameter {
256 parameter: parameter.into(),
257 }
258 }
259
260 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 pub fn datum_transform_error<S: Into<String>>(reason: S) -> Self {
270 Self::DatumTransformError {
271 reason: reason.into(),
272 }
273 }
274
275 pub fn projection_init_error<S: Into<String>>(reason: S) -> Self {
277 Self::ProjectionInitError {
278 reason: reason.into(),
279 }
280 }
281
282 pub fn unsupported_projection<S: Into<String>>(projection: S) -> Self {
284 Self::UnsupportedProjection {
285 projection: projection.into(),
286 }
287 }
288
289 pub fn numerical_error<S: Into<String>>(operation: S) -> Self {
291 Self::NumericalError {
292 operation: operation.into(),
293 }
294 }
295
296 pub fn convergence_error(iterations: usize) -> Self {
298 Self::ConvergenceError { iterations }
299 }
300
301 #[cfg(feature = "std")]
303 pub fn from_proj4rs<S: Into<String>>(message: S) -> Self {
304 Self::Proj4rsError(message.into())
305 }
306
307 pub fn other<S: Into<String>>(message: S) -> Self {
309 Self::Other(message.into())
310 }
311}
312
313#[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}