Skip to main content

oxigdal_core/error/
methods.rs

1//! Method implementations for error types
2
3use super::builder::ErrorContext;
4use super::types::*;
5
6#[cfg(feature = "std")]
7use std::path::Path;
8
9impl IoError {
10    /// Get the error code for this I/O error
11    pub fn code(&self) -> &'static str {
12        match self {
13            Self::NotFound { .. } => "E100",
14            Self::PermissionDenied { .. } => "E101",
15            Self::Network { .. } => "E102",
16            Self::UnexpectedEof { .. } => "E103",
17            Self::Read { .. } => "E104",
18            Self::Write { .. } => "E105",
19            Self::Seek { .. } => "E106",
20            Self::Http { .. } => "E107",
21        }
22    }
23
24    /// Get a helpful suggestion for fixing this I/O error
25    pub fn suggestion(&self) -> Option<&'static str> {
26        match self {
27            Self::NotFound { .. } => Some("Verify the file path is correct and the file exists"),
28            Self::PermissionDenied { .. } => {
29                Some("Check file permissions or run with appropriate privileges")
30            }
31            Self::Network { .. } => Some("Check network connectivity and firewall settings"),
32            Self::UnexpectedEof { .. } => Some("The file may be truncated or corrupted"),
33            Self::Read { .. } => {
34                Some("Ensure the file is accessible and not locked by another process")
35            }
36            Self::Write { .. } => Some("Check available disk space and write permissions"),
37            Self::Seek { .. } => Some("The file position may be invalid for this file type"),
38            Self::Http { status, .. } if *status == 404 => {
39                Some("The requested resource was not found on the server")
40            }
41            Self::Http { status, .. } if *status == 403 => {
42                Some("Access to this resource is forbidden. Check authentication credentials")
43            }
44            Self::Http { status, .. } if *status >= 500 => {
45                Some("The server is experiencing issues. Try again later")
46            }
47            Self::Http { .. } => Some("Check the HTTP request parameters and server status"),
48        }
49    }
50
51    /// Get additional context about this I/O error
52    pub fn context(&self) -> ErrorContext {
53        match self {
54            Self::NotFound { path } => {
55                ErrorContext::new("file_not_found").with_detail("path", path.clone())
56            }
57            Self::PermissionDenied { path } => {
58                ErrorContext::new("permission_denied").with_detail("path", path.clone())
59            }
60            Self::Network { message } => {
61                ErrorContext::new("network_error").with_detail("message", message.clone())
62            }
63            Self::UnexpectedEof { offset } => {
64                ErrorContext::new("unexpected_eof").with_detail("offset", offset.to_string())
65            }
66            Self::Read { message } => {
67                ErrorContext::new("read_error").with_detail("message", message.clone())
68            }
69            Self::Write { message } => {
70                ErrorContext::new("write_error").with_detail("message", message.clone())
71            }
72            Self::Seek { position } => {
73                ErrorContext::new("seek_error").with_detail("position", position.to_string())
74            }
75            Self::Http { status, message } => ErrorContext::new("http_error")
76                .with_detail("status", status.to_string())
77                .with_detail("message", message.clone()),
78        }
79    }
80}
81
82impl FormatError {
83    /// Get the error code for this format error
84    pub fn code(&self) -> &'static str {
85        match self {
86            Self::InvalidMagic { .. } => "E200",
87            Self::InvalidHeader { .. } => "E201",
88            Self::UnsupportedVersion { .. } => "E202",
89            Self::InvalidTag { .. } => "E203",
90            Self::MissingTag { .. } => "E204",
91            Self::InvalidDataType { .. } => "E205",
92            Self::CorruptData { .. } => "E206",
93            Self::InvalidGeoKey { .. } => "E207",
94        }
95    }
96
97    /// Get a helpful suggestion for fixing this format error
98    pub fn suggestion(&self) -> Option<&'static str> {
99        match self {
100            Self::InvalidMagic { .. } => {
101                Some("The file may not be in the expected format. Verify the file type")
102            }
103            Self::InvalidHeader { .. } => {
104                Some("The file header is corrupted or invalid. Try opening a different file")
105            }
106            Self::UnsupportedVersion { .. } => Some(
107                "This file version is not supported. Consider converting to a newer or older version",
108            ),
109            Self::InvalidTag { .. } => {
110                Some("The file contains invalid metadata tags. The file may be corrupted")
111            }
112            Self::MissingTag { .. } => {
113                Some("Required metadata is missing. The file may be incomplete or corrupted")
114            }
115            Self::InvalidDataType { .. } => {
116                Some("The data type is not recognized. The file may be from a newer version")
117            }
118            Self::CorruptData { .. } => {
119                Some("Data corruption detected. Try recovering from a backup")
120            }
121            Self::InvalidGeoKey { .. } => {
122                Some("Geographic metadata is invalid. Check the coordinate reference system")
123            }
124        }
125    }
126
127    /// Get additional context about this format error
128    pub fn context(&self) -> ErrorContext {
129        match self {
130            Self::InvalidMagic { expected, actual } => ErrorContext::new("invalid_magic")
131                .with_detail("expected", format!("{:?}", expected))
132                .with_detail("actual", format!("{:?}", actual)),
133            Self::InvalidHeader { message } => {
134                ErrorContext::new("invalid_header").with_detail("message", message.clone())
135            }
136            Self::UnsupportedVersion { version } => {
137                ErrorContext::new("unsupported_version").with_detail("version", version.to_string())
138            }
139            Self::InvalidTag { tag, message } => ErrorContext::new("invalid_tag")
140                .with_detail("tag", tag.to_string())
141                .with_detail("message", message.clone()),
142            Self::MissingTag { tag } => {
143                ErrorContext::new("missing_tag").with_detail("tag", tag.to_string())
144            }
145            Self::InvalidDataType { type_id } => {
146                ErrorContext::new("invalid_data_type").with_detail("type_id", type_id.to_string())
147            }
148            Self::CorruptData { offset, message } => ErrorContext::new("corrupt_data")
149                .with_detail("offset", offset.to_string())
150                .with_detail("message", message.clone()),
151            Self::InvalidGeoKey { key_id, message } => ErrorContext::new("invalid_geokey")
152                .with_detail("key_id", key_id.to_string())
153                .with_detail("message", message.clone()),
154        }
155    }
156}
157
158impl CrsError {
159    /// Get the error code for this CRS error
160    pub fn code(&self) -> &'static str {
161        match self {
162            Self::UnknownCrs { .. } => "E300",
163            Self::InvalidWkt { .. } => "E301",
164            Self::InvalidEpsg { .. } => "E302",
165            Self::TransformationError { .. } => "E303",
166            Self::DatumNotFound { .. } => "E304",
167        }
168    }
169
170    /// Get a helpful suggestion for fixing this CRS error
171    pub fn suggestion(&self) -> Option<&'static str> {
172        match self {
173            Self::UnknownCrs { .. } => {
174                Some("Verify the CRS identifier or use a standard EPSG code")
175            }
176            Self::InvalidWkt { .. } => {
177                Some("Check the WKT string syntax. Ensure proper bracketing and spacing")
178            }
179            Self::InvalidEpsg { .. } => Some("Use a valid EPSG code from https://epsg.io"),
180            Self::TransformationError { .. } => {
181                Some("Ensure both CRS are compatible and transformation parameters are available")
182            }
183            Self::DatumNotFound { .. } => {
184                Some("The datum definition may be missing. Check CRS database installation")
185            }
186        }
187    }
188
189    /// Get additional context about this CRS error
190    pub fn context(&self) -> ErrorContext {
191        match self {
192            Self::UnknownCrs { identifier } => {
193                ErrorContext::new("unknown_crs").with_detail("identifier", identifier.clone())
194            }
195            Self::InvalidWkt { message } => {
196                ErrorContext::new("invalid_wkt").with_detail("message", message.clone())
197            }
198            Self::InvalidEpsg { code } => {
199                ErrorContext::new("invalid_epsg").with_detail("code", code.to_string())
200            }
201            Self::TransformationError {
202                source_crs,
203                target_crs,
204                message,
205            } => ErrorContext::new("transformation_error")
206                .with_detail("source_crs", source_crs.clone())
207                .with_detail("target_crs", target_crs.clone())
208                .with_detail("message", message.clone()),
209            Self::DatumNotFound { datum } => {
210                ErrorContext::new("datum_not_found").with_detail("datum", datum.clone())
211            }
212        }
213    }
214}
215
216impl CompressionError {
217    /// Get the error code for this compression error
218    pub fn code(&self) -> &'static str {
219        match self {
220            Self::UnknownMethod { .. } => "E400",
221            Self::DecompressionFailed { .. } => "E401",
222            Self::CompressionFailed { .. } => "E402",
223            Self::InvalidData { .. } => "E403",
224        }
225    }
226
227    /// Get a helpful suggestion for fixing this compression error
228    pub fn suggestion(&self) -> Option<&'static str> {
229        match self {
230            Self::UnknownMethod { .. } => Some(
231                "The compression method is not supported. Check available compression features",
232            ),
233            Self::DecompressionFailed { .. } => {
234                Some("The compressed data may be corrupted. Try a backup or re-download the file")
235            }
236            Self::CompressionFailed { .. } => Some(
237                "Compression failed. Try a different compression method or lower compression level",
238            ),
239            Self::InvalidData { .. } => Some("The compressed data is invalid or corrupted"),
240        }
241    }
242
243    /// Get additional context about this compression error
244    pub fn context(&self) -> ErrorContext {
245        match self {
246            Self::UnknownMethod { method } => {
247                ErrorContext::new("unknown_compression").with_detail("method", method.to_string())
248            }
249            Self::DecompressionFailed { message } => {
250                ErrorContext::new("decompression_failed").with_detail("message", message.clone())
251            }
252            Self::CompressionFailed { message } => {
253                ErrorContext::new("compression_failed").with_detail("message", message.clone())
254            }
255            Self::InvalidData { message } => {
256                ErrorContext::new("invalid_compressed_data").with_detail("message", message.clone())
257            }
258        }
259    }
260}
261
262impl OxiGdalError {
263    /// Create an allocation error
264    pub fn allocation_error(message: impl Into<String>) -> Self {
265        Self::Internal {
266            message: format!("Allocation error: {}", message.into()),
267        }
268    }
269
270    /// Create an allocation error builder with rich context
271    pub fn allocation_error_builder(message: impl Into<String>) -> crate::error::ErrorBuilder {
272        crate::error::ErrorBuilder::new(Self::allocation_error(message))
273    }
274
275    /// Create an invalid state error
276    pub fn invalid_state(message: impl Into<String>) -> Self {
277        Self::Internal {
278            message: format!("Invalid state: {}", message.into()),
279        }
280    }
281
282    /// Create an invalid state error builder with rich context
283    pub fn invalid_state_builder(message: impl Into<String>) -> crate::error::ErrorBuilder {
284        crate::error::ErrorBuilder::new(Self::invalid_state(message))
285    }
286
287    /// Create an invalid operation error
288    pub fn invalid_operation(message: impl Into<String>) -> Self {
289        Self::NotSupported {
290            operation: message.into(),
291        }
292    }
293
294    /// Create an invalid operation error builder with rich context
295    pub fn invalid_operation_builder(message: impl Into<String>) -> crate::error::ErrorBuilder {
296        crate::error::ErrorBuilder::new(Self::invalid_operation(message))
297    }
298
299    /// Create an I/O error from a message
300    pub fn io_error(message: impl Into<String>) -> Self {
301        Self::Io(IoError::Read {
302            message: message.into(),
303        })
304    }
305
306    /// Create an I/O error builder with rich context
307    pub fn io_error_builder(message: impl Into<String>) -> crate::error::ErrorBuilder {
308        crate::error::ErrorBuilder::new(Self::io_error(message))
309    }
310
311    /// Create an invalid parameter error with parameter name
312    pub fn invalid_parameter(parameter: &'static str, message: impl Into<String>) -> Self {
313        Self::InvalidParameter {
314            parameter,
315            message: message.into(),
316        }
317    }
318
319    /// Create an invalid parameter error builder with rich context
320    pub fn invalid_parameter_builder(
321        parameter: &'static str,
322        message: impl Into<String>,
323    ) -> crate::error::ErrorBuilder {
324        crate::error::ErrorBuilder::new(Self::invalid_parameter(parameter, message))
325    }
326
327    /// Create a not supported error
328    pub fn not_supported(operation: impl Into<String>) -> Self {
329        Self::NotSupported {
330            operation: operation.into(),
331        }
332    }
333
334    /// Create a not supported error builder with rich context
335    pub fn not_supported_builder(operation: impl Into<String>) -> crate::error::ErrorBuilder {
336        crate::error::ErrorBuilder::new(Self::not_supported(operation))
337    }
338
339    /// Create an I/O error from a file path
340    #[cfg(feature = "std")]
341    pub fn from_path(path: &Path, kind: std::io::ErrorKind) -> Self {
342        let path_str = path.display().to_string();
343        match kind {
344            std::io::ErrorKind::NotFound => Self::Io(IoError::NotFound { path: path_str }),
345            std::io::ErrorKind::PermissionDenied => {
346                Self::Io(IoError::PermissionDenied { path: path_str })
347            }
348            _ => Self::Io(IoError::Read {
349                message: format!("Error accessing {}", path_str),
350            }),
351        }
352    }
353
354    /// Create an I/O error builder from a file path with rich context
355    #[cfg(feature = "std")]
356    pub fn from_path_builder(path: &Path, kind: std::io::ErrorKind) -> crate::error::ErrorBuilder {
357        crate::error::ErrorBuilder::new(Self::from_path(path, kind)).with_path(path)
358    }
359
360    /// Get the error code for this error
361    ///
362    /// Error codes are stable across versions and can be used for documentation
363    /// and error handling.
364    pub fn code(&self) -> &'static str {
365        match self {
366            Self::Io(e) => e.code(),
367            Self::Format(e) => e.code(),
368            Self::Crs(e) => e.code(),
369            Self::Compression(e) => e.code(),
370            Self::InvalidParameter { .. } => "E001",
371            Self::NotSupported { .. } => "E002",
372            Self::OutOfBounds { .. } => "E003",
373            Self::Internal { .. } => "E004",
374        }
375    }
376
377    /// Get a helpful suggestion for fixing this error
378    ///
379    /// Returns a human-readable suggestion that can help users resolve the error.
380    pub fn suggestion(&self) -> Option<&'static str> {
381        match self {
382            Self::Io(e) => e.suggestion(),
383            Self::Format(e) => e.suggestion(),
384            Self::Crs(e) => e.suggestion(),
385            Self::Compression(e) => e.suggestion(),
386            Self::InvalidParameter { .. } => {
387                Some("Check the parameter documentation for valid values")
388            }
389            Self::NotSupported { .. } => {
390                Some("Check if the feature is enabled or use an alternative approach")
391            }
392            Self::OutOfBounds { .. } => Some("Verify the indices are within valid range"),
393            Self::Internal { .. } => {
394                Some("This is likely a bug. Please report it with steps to reproduce")
395            }
396        }
397    }
398
399    /// Get additional context about this error
400    ///
401    /// Returns structured context information that can help with debugging.
402    pub fn context(&self) -> ErrorContext {
403        match self {
404            Self::Io(e) => e.context(),
405            Self::Format(e) => e.context(),
406            Self::Crs(e) => e.context(),
407            Self::Compression(e) => e.context(),
408            Self::InvalidParameter { parameter, message } => {
409                ErrorContext::new("parameter_validation")
410                    .with_detail("parameter", *parameter)
411                    .with_detail("reason", message.clone())
412            }
413            Self::NotSupported { operation } => ErrorContext::new("unsupported_operation")
414                .with_detail("operation", operation.clone()),
415            Self::OutOfBounds { message } => {
416                ErrorContext::new("bounds_check").with_detail("reason", message.clone())
417            }
418            Self::Internal { message } => {
419                ErrorContext::new("internal_error").with_detail("details", message.clone())
420            }
421        }
422    }
423}