use thiserror::Error;
pub type Result<T> = std::result::Result<T, ForecastError>;
#[derive(Error, Debug, Clone, PartialEq)]
pub enum ForecastError {
#[error("empty input data")]
EmptyData,
#[error("insufficient data: need at least {needed}, got {got}{}", .hint.as_deref().map(|h| format!(" ({})", h)).unwrap_or_default())]
InsufficientData {
needed: usize,
got: usize,
hint: Option<String>,
},
#[error("invalid parameter: {0}")]
InvalidParameter(String),
#[error("dimension mismatch: expected {expected}, got {got}")]
DimensionMismatch { expected: usize, got: usize },
#[error("timestamp error: {0}")]
TimestampError(String),
#[error("{}", match model { Some(m) => format!("Model '{}' must be fitted before prediction", m), None => "model must be fitted before prediction".to_string() })]
FitRequired { model: Option<String> },
#[error("sub-model '{model_name}' failed: {source}")]
SubModelError {
model_name: String,
source: Box<ForecastError>,
},
#[error("missing values detected in data")]
MissingValues,
#[error("could not infer frequency: {0}")]
FrequencyInference(String),
#[error("index out of bounds: {index} (size: {size})")]
IndexOutOfBounds { index: usize, size: usize },
#[error("computation error: {0}")]
ComputationError(String),
#[error("convergence failure: {0}")]
ConvergenceFailure(String),
#[error("singular matrix: {0}")]
SingularMatrix(String),
#[error("serialization error: {0}")]
SerializationError(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_messages_are_descriptive() {
let err = ForecastError::EmptyData;
assert_eq!(err.to_string(), "empty input data");
let err = ForecastError::InsufficientData {
needed: 10,
got: 5,
hint: None,
};
assert_eq!(
err.to_string(),
"insufficient data: need at least 10, got 5"
);
let err = ForecastError::InsufficientData {
needed: 10,
got: 5,
hint: Some("test hint".into()),
};
assert_eq!(
err.to_string(),
"insufficient data: need at least 10, got 5 (test hint)"
);
let err = ForecastError::InvalidParameter("window must be positive".to_string());
assert_eq!(
err.to_string(),
"invalid parameter: window must be positive"
);
let err = ForecastError::DimensionMismatch {
expected: 3,
got: 2,
};
assert_eq!(err.to_string(), "dimension mismatch: expected 3, got 2");
let err = ForecastError::FitRequired { model: None };
assert_eq!(err.to_string(), "model must be fitted before prediction");
let err = ForecastError::FitRequired {
model: Some("TestModel".to_string()),
};
assert_eq!(
err.to_string(),
"Model 'TestModel' must be fitted before prediction"
);
let inner = ForecastError::EmptyData;
let err = ForecastError::SubModelError {
model_name: "SES".to_string(),
source: Box::new(inner),
};
assert_eq!(err.to_string(), "sub-model 'SES' failed: empty input data");
}
#[test]
fn errors_are_clonable_and_comparable() {
let err1 = ForecastError::EmptyData;
let err2 = err1.clone();
assert_eq!(err1, err2);
}
}