use thiserror::Error;
pub type Result<T> = std::result::Result<T, TemporalError>;
#[derive(Debug, Error)]
pub enum TemporalError {
#[error("Invalid temporal input: {0}")]
InvalidInput(String),
#[error("Insufficient temporal data: {0}")]
InsufficientData(String),
#[error("Time index out of bounds: {index} (valid range: {min}..{max})")]
TimeIndexOutOfBounds {
index: usize,
min: usize,
max: usize,
},
#[error("Invalid time range: start={start}, end={end}")]
InvalidTimeRange {
start: String,
end: String,
},
#[error("Gap detected in time series at position {position}")]
GapDetected {
position: usize,
},
#[error("Temporal dimension mismatch: expected {expected}, got {actual}")]
DimensionMismatch {
expected: String,
actual: String,
},
#[error("Invalid temporal parameter '{param}': {reason}")]
InvalidParameter {
param: String,
reason: String,
},
#[error("Compositing failed: {0}")]
CompositingError(String),
#[error("Temporal interpolation failed: {0}")]
InterpolationError(String),
#[error("Temporal aggregation failed: {0}")]
AggregationError(String),
#[error("Change detection failed: {0}")]
ChangeDetectionError(String),
#[error("Trend analysis failed: {0}")]
TrendAnalysisError(String),
#[error("Phenology analysis failed: {0}")]
PhenologyError(String),
#[error("Data cube operation failed: {0}")]
DataCubeError(String),
#[error("Temporal metadata error: {0}")]
MetadataError(String),
#[error("Storage error: {0}")]
StorageError(String),
#[cfg(feature = "zarr")]
#[error("Zarr error: {0}")]
ZarrError(String),
#[error("Analytics error: {0}")]
AnalyticsError(#[from] oxigdal_analytics::error::AnalyticsError),
#[error("Core error: {0}")]
CoreError(#[from] oxigdal_core::error::OxiGdalError),
#[error("DateTime parse error: {0}")]
DateTimeParseError(String),
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Serialization error: {0}")]
SerializationError(#[from] serde_json::Error),
}
impl TemporalError {
pub fn invalid_input(msg: impl Into<String>) -> Self {
Self::InvalidInput(msg.into())
}
pub fn insufficient_data(msg: impl Into<String>) -> Self {
Self::InsufficientData(msg.into())
}
pub fn time_index_out_of_bounds(index: usize, min: usize, max: usize) -> Self {
Self::TimeIndexOutOfBounds { index, min, max }
}
pub fn invalid_time_range(start: impl Into<String>, end: impl Into<String>) -> Self {
Self::InvalidTimeRange {
start: start.into(),
end: end.into(),
}
}
pub fn gap_detected(position: usize) -> Self {
Self::GapDetected { position }
}
pub fn dimension_mismatch(expected: impl Into<String>, actual: impl Into<String>) -> Self {
Self::DimensionMismatch {
expected: expected.into(),
actual: actual.into(),
}
}
pub fn invalid_parameter(param: impl Into<String>, reason: impl Into<String>) -> Self {
Self::InvalidParameter {
param: param.into(),
reason: reason.into(),
}
}
pub fn compositing_error(msg: impl Into<String>) -> Self {
Self::CompositingError(msg.into())
}
pub fn interpolation_error(msg: impl Into<String>) -> Self {
Self::InterpolationError(msg.into())
}
pub fn aggregation_error(msg: impl Into<String>) -> Self {
Self::AggregationError(msg.into())
}
pub fn change_detection_error(msg: impl Into<String>) -> Self {
Self::ChangeDetectionError(msg.into())
}
pub fn trend_analysis_error(msg: impl Into<String>) -> Self {
Self::TrendAnalysisError(msg.into())
}
pub fn phenology_error(msg: impl Into<String>) -> Self {
Self::PhenologyError(msg.into())
}
pub fn datacube_error(msg: impl Into<String>) -> Self {
Self::DataCubeError(msg.into())
}
pub fn metadata_error(msg: impl Into<String>) -> Self {
Self::MetadataError(msg.into())
}
pub fn storage_error(msg: impl Into<String>) -> Self {
Self::StorageError(msg.into())
}
#[cfg(feature = "zarr")]
pub fn zarr_error(msg: impl Into<String>) -> Self {
Self::ZarrError(msg.into())
}
pub fn datetime_parse_error(msg: impl Into<String>) -> Self {
Self::DateTimeParseError(msg.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = TemporalError::invalid_input("test");
assert!(matches!(err, TemporalError::InvalidInput(_)));
let err = TemporalError::dimension_mismatch("100x100x10", "100x100x5");
assert!(matches!(err, TemporalError::DimensionMismatch { .. }));
let err = TemporalError::time_index_out_of_bounds(15, 0, 10);
assert!(matches!(err, TemporalError::TimeIndexOutOfBounds { .. }));
}
#[test]
fn test_error_display() {
let err = TemporalError::invalid_input("invalid timestamp format");
assert_eq!(
format!("{}", err),
"Invalid temporal input: invalid timestamp format"
);
let err = TemporalError::dimension_mismatch("100x100x10", "100x100x5");
assert_eq!(
format!("{}", err),
"Temporal dimension mismatch: expected 100x100x10, got 100x100x5"
);
let err = TemporalError::time_index_out_of_bounds(15, 0, 10);
assert_eq!(
format!("{}", err),
"Time index out of bounds: 15 (valid range: 0..10)"
);
}
#[test]
fn test_invalid_time_range() {
let err = TemporalError::invalid_time_range("2023-12-31", "2023-01-01");
assert!(matches!(err, TemporalError::InvalidTimeRange { .. }));
assert!(format!("{}", err).contains("2023-12-31"));
assert!(format!("{}", err).contains("2023-01-01"));
}
#[test]
fn test_gap_detected() {
let err = TemporalError::gap_detected(5);
assert!(matches!(err, TemporalError::GapDetected { position: 5 }));
}
}