Skip to main content

oxigdal_temporal/
error.rs

1//! Error types for OxiGDAL Temporal Analysis
2//!
3//! This module provides comprehensive error handling for all temporal operations.
4//! All errors implement `std::error::Error` and are designed for production use.
5
6use thiserror::Error;
7
8/// Result type alias for temporal operations
9pub type Result<T> = std::result::Result<T, TemporalError>;
10
11/// Comprehensive error types for temporal analysis operations
12#[derive(Debug, Error)]
13pub enum TemporalError {
14    /// Invalid temporal input
15    #[error("Invalid temporal input: {0}")]
16    InvalidInput(String),
17
18    /// Insufficient temporal data for computation
19    #[error("Insufficient temporal data: {0}")]
20    InsufficientData(String),
21
22    /// Time index out of bounds
23    #[error("Time index out of bounds: {index} (valid range: {min}..{max})")]
24    TimeIndexOutOfBounds {
25        /// The invalid index that was provided
26        index: usize,
27        /// Minimum valid index
28        min: usize,
29        /// Maximum valid index (exclusive)
30        max: usize,
31    },
32
33    /// Invalid time range
34    #[error("Invalid time range: start={start}, end={end}")]
35    InvalidTimeRange {
36        /// Start of the invalid range
37        start: String,
38        /// End of the invalid range
39        end: String,
40    },
41
42    /// Gap in time series data
43    #[error("Gap detected in time series at position {position}")]
44    GapDetected {
45        /// Position where the gap was detected
46        position: usize,
47    },
48
49    /// Temporal dimension mismatch
50    #[error("Temporal dimension mismatch: expected {expected}, got {actual}")]
51    DimensionMismatch {
52        /// Expected dimension specification
53        expected: String,
54        /// Actual dimension specification
55        actual: String,
56    },
57
58    /// Invalid temporal parameter
59    #[error("Invalid temporal parameter '{param}': {reason}")]
60    InvalidParameter {
61        /// Name of the invalid parameter
62        param: String,
63        /// Reason why the parameter is invalid
64        reason: String,
65    },
66
67    /// Compositing operation failed
68    #[error("Compositing failed: {0}")]
69    CompositingError(String),
70
71    /// Interpolation failed
72    #[error("Temporal interpolation failed: {0}")]
73    InterpolationError(String),
74
75    /// Aggregation failed
76    #[error("Temporal aggregation failed: {0}")]
77    AggregationError(String),
78
79    /// Change detection failed
80    #[error("Change detection failed: {0}")]
81    ChangeDetectionError(String),
82
83    /// Trend analysis failed
84    #[error("Trend analysis failed: {0}")]
85    TrendAnalysisError(String),
86
87    /// Phenology analysis failed
88    #[error("Phenology analysis failed: {0}")]
89    PhenologyError(String),
90
91    /// Data cube operation failed
92    #[error("Data cube operation failed: {0}")]
93    DataCubeError(String),
94
95    /// Temporal metadata error
96    #[error("Temporal metadata error: {0}")]
97    MetadataError(String),
98
99    /// Storage backend error
100    #[error("Storage error: {0}")]
101    StorageError(String),
102
103    /// Zarr driver error
104    #[cfg(feature = "zarr")]
105    #[error("Zarr error: {0}")]
106    ZarrError(String),
107
108    /// Analytics library error
109    #[error("Analytics error: {0}")]
110    AnalyticsError(#[from] oxigdal_analytics::error::AnalyticsError),
111
112    /// Core library error
113    #[error("Core error: {0}")]
114    CoreError(#[from] oxigdal_core::error::OxiGdalError),
115
116    /// Date/time parsing error
117    #[error("DateTime parse error: {0}")]
118    DateTimeParseError(String),
119
120    /// I/O error
121    #[error("I/O error: {0}")]
122    IoError(#[from] std::io::Error),
123
124    /// Serialization error
125    #[error("Serialization error: {0}")]
126    SerializationError(#[from] serde_json::Error),
127}
128
129impl TemporalError {
130    /// Create an invalid input error
131    pub fn invalid_input(msg: impl Into<String>) -> Self {
132        Self::InvalidInput(msg.into())
133    }
134
135    /// Create an insufficient data error
136    pub fn insufficient_data(msg: impl Into<String>) -> Self {
137        Self::InsufficientData(msg.into())
138    }
139
140    /// Create a time index out of bounds error
141    pub fn time_index_out_of_bounds(index: usize, min: usize, max: usize) -> Self {
142        Self::TimeIndexOutOfBounds { index, min, max }
143    }
144
145    /// Create an invalid time range error
146    pub fn invalid_time_range(start: impl Into<String>, end: impl Into<String>) -> Self {
147        Self::InvalidTimeRange {
148            start: start.into(),
149            end: end.into(),
150        }
151    }
152
153    /// Create a gap detected error
154    pub fn gap_detected(position: usize) -> Self {
155        Self::GapDetected { position }
156    }
157
158    /// Create a dimension mismatch error
159    pub fn dimension_mismatch(expected: impl Into<String>, actual: impl Into<String>) -> Self {
160        Self::DimensionMismatch {
161            expected: expected.into(),
162            actual: actual.into(),
163        }
164    }
165
166    /// Create an invalid parameter error
167    pub fn invalid_parameter(param: impl Into<String>, reason: impl Into<String>) -> Self {
168        Self::InvalidParameter {
169            param: param.into(),
170            reason: reason.into(),
171        }
172    }
173
174    /// Create a compositing error
175    pub fn compositing_error(msg: impl Into<String>) -> Self {
176        Self::CompositingError(msg.into())
177    }
178
179    /// Create an interpolation error
180    pub fn interpolation_error(msg: impl Into<String>) -> Self {
181        Self::InterpolationError(msg.into())
182    }
183
184    /// Create an aggregation error
185    pub fn aggregation_error(msg: impl Into<String>) -> Self {
186        Self::AggregationError(msg.into())
187    }
188
189    /// Create a change detection error
190    pub fn change_detection_error(msg: impl Into<String>) -> Self {
191        Self::ChangeDetectionError(msg.into())
192    }
193
194    /// Create a trend analysis error
195    pub fn trend_analysis_error(msg: impl Into<String>) -> Self {
196        Self::TrendAnalysisError(msg.into())
197    }
198
199    /// Create a phenology error
200    pub fn phenology_error(msg: impl Into<String>) -> Self {
201        Self::PhenologyError(msg.into())
202    }
203
204    /// Create a data cube error
205    pub fn datacube_error(msg: impl Into<String>) -> Self {
206        Self::DataCubeError(msg.into())
207    }
208
209    /// Create a metadata error
210    pub fn metadata_error(msg: impl Into<String>) -> Self {
211        Self::MetadataError(msg.into())
212    }
213
214    /// Create a storage error
215    pub fn storage_error(msg: impl Into<String>) -> Self {
216        Self::StorageError(msg.into())
217    }
218
219    /// Create a Zarr error
220    #[cfg(feature = "zarr")]
221    pub fn zarr_error(msg: impl Into<String>) -> Self {
222        Self::ZarrError(msg.into())
223    }
224
225    /// Create a date/time parse error
226    pub fn datetime_parse_error(msg: impl Into<String>) -> Self {
227        Self::DateTimeParseError(msg.into())
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn test_error_creation() {
237        let err = TemporalError::invalid_input("test");
238        assert!(matches!(err, TemporalError::InvalidInput(_)));
239
240        let err = TemporalError::dimension_mismatch("100x100x10", "100x100x5");
241        assert!(matches!(err, TemporalError::DimensionMismatch { .. }));
242
243        let err = TemporalError::time_index_out_of_bounds(15, 0, 10);
244        assert!(matches!(err, TemporalError::TimeIndexOutOfBounds { .. }));
245    }
246
247    #[test]
248    fn test_error_display() {
249        let err = TemporalError::invalid_input("invalid timestamp format");
250        assert_eq!(
251            format!("{}", err),
252            "Invalid temporal input: invalid timestamp format"
253        );
254
255        let err = TemporalError::dimension_mismatch("100x100x10", "100x100x5");
256        assert_eq!(
257            format!("{}", err),
258            "Temporal dimension mismatch: expected 100x100x10, got 100x100x5"
259        );
260
261        let err = TemporalError::time_index_out_of_bounds(15, 0, 10);
262        assert_eq!(
263            format!("{}", err),
264            "Time index out of bounds: 15 (valid range: 0..10)"
265        );
266    }
267
268    #[test]
269    fn test_invalid_time_range() {
270        let err = TemporalError::invalid_time_range("2023-12-31", "2023-01-01");
271        assert!(matches!(err, TemporalError::InvalidTimeRange { .. }));
272        assert!(format!("{}", err).contains("2023-12-31"));
273        assert!(format!("{}", err).contains("2023-01-01"));
274    }
275
276    #[test]
277    fn test_gap_detected() {
278        let err = TemporalError::gap_detected(5);
279        assert!(matches!(err, TemporalError::GapDetected { position: 5 }));
280    }
281}