use thiserror::Error;
#[derive(Debug, Clone, PartialEq)]
pub enum MathErrorKind {
Overflow,
Underflow,
PrecisionLoss,
DivisionByZero,
InvalidInput,
NotFinite,
OutOfRange,
}
#[derive(Error, Debug)]
pub enum AstroError {
#[error("Invalid date {year}-{month:02}-{day:02}: {message}")]
InvalidDate {
year: i32,
month: i32,
day: i32,
message: String,
},
#[error("Math error in {operation} ({kind:?}): {message}")]
MathError {
operation: String,
kind: MathErrorKind,
message: String,
},
#[error("External library error in {function}: status {status_code} - {message}")]
ExternalLibraryError {
function: String,
status_code: i32,
message: String,
},
#[error("Data error ({file_type} - {operation}): {message}")]
DataError {
file_type: String,
operation: String,
message: String,
},
#[error("Calculation error in {context}: {message}")]
CalculationError { context: String, message: String },
}
pub type AstroResult<T> = Result<T, AstroError>;
impl AstroError {
pub fn invalid_date(year: i32, month: i32, day: i32, reason: &str) -> Self {
Self::InvalidDate {
year,
month,
day,
message: reason.to_string(),
}
}
pub fn math_error(operation: &str, kind: MathErrorKind, reason: &str) -> Self {
Self::MathError {
operation: operation.to_string(),
kind,
message: reason.to_string(),
}
}
pub fn external_library_error(function: &str, status_code: i32, message: &str) -> Self {
Self::ExternalLibraryError {
function: function.to_string(),
status_code,
message: message.to_string(),
}
}
pub fn data_error(file_type: &str, operation: &str, reason: &str) -> Self {
Self::DataError {
file_type: file_type.to_string(),
operation: operation.to_string(),
message: reason.to_string(),
}
}
pub fn calculation_error(context: &str, reason: &str) -> Self {
Self::CalculationError {
context: context.to_string(),
message: reason.to_string(),
}
}
pub fn is_recoverable(&self) -> bool {
match self {
Self::DataError { .. } => true,
Self::InvalidDate { .. } => false,
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_date_error() {
let err = AstroError::invalid_date(2000, 13, 1, "month out of range");
assert_eq!(
err.to_string(),
"Invalid date 2000-13-01: month out of range"
);
}
#[test]
fn test_math_error_with_kind() {
let err = AstroError::math_error(
"nanosecond addition",
MathErrorKind::Overflow,
"value too large",
);
assert!(err.to_string().contains("Math error"));
assert!(err.to_string().contains("Overflow"));
}
#[test]
fn test_external_library_error() {
let err = AstroError::external_library_error("telescope_driver", -2, "mount error");
assert!(err.to_string().contains("mount error"));
assert!(err.to_string().contains("telescope_driver"));
assert!(err.to_string().contains("status -2"));
}
#[test]
fn test_data_error() {
let err = AstroError::data_error("IERS Bulletin A", "download", "network timeout");
assert!(err
.to_string()
.contains("Data error (IERS Bulletin A - download)"));
}
#[test]
fn test_calculation_error() {
let err = AstroError::calculation_error("orbit propagation", "insufficient data");
assert!(err
.to_string()
.contains("Calculation error in orbit propagation"));
}
#[test]
fn test_recoverable_errors() {
assert!(AstroError::data_error("catalog", "download", "timeout").is_recoverable());
assert!(!AstroError::invalid_date(2000, 13, 1, "bad month").is_recoverable());
}
#[test]
fn test_send_sync() {
fn _assert_send<T: Send>() {}
fn _assert_sync<T: Sync>() {}
_assert_send::<AstroError>();
_assert_sync::<AstroError>();
}
#[test]
fn test_non_recoverable_errors() {
let math_err =
AstroError::math_error("calculation", MathErrorKind::Overflow, "value too large");
assert!(!math_err.is_recoverable());
let lib_err = AstroError::external_library_error("telescope_driver", -1, "mount error");
assert!(!lib_err.is_recoverable());
let calc_err = AstroError::calculation_error("orbit_propagation", "insufficient data");
assert!(!calc_err.is_recoverable());
}
}