1use thiserror::Error;
38
39#[derive(Debug, Clone, PartialEq)]
44pub enum MathErrorKind {
45 Overflow,
47 Underflow,
49 PrecisionLoss,
51 DivisionByZero,
53 InvalidInput,
55 NotFinite,
57 OutOfRange,
59}
60
61#[derive(Error, Debug)]
68pub enum AstroError {
69 #[error("Invalid date {year}-{month:02}-{day:02}: {message}")]
71 InvalidDate {
72 year: i32,
73 month: i32,
74 day: i32,
75 message: String,
76 },
77
78 #[error("Math error in {operation} ({kind:?}): {message}")]
80 MathError {
81 operation: String,
82 kind: MathErrorKind,
83 message: String,
84 },
85
86 #[error("External library error in {function}: status {status_code} - {message}")]
88 ExternalLibraryError {
89 function: String,
90 status_code: i32,
91 message: String,
92 },
93
94 #[error("Data error ({file_type} - {operation}): {message}")]
98 DataError {
99 file_type: String,
100 operation: String,
101 message: String,
102 },
103
104 #[error("Calculation error in {context}: {message}")]
106 CalculationError { context: String, message: String },
107}
108
109pub type AstroResult<T> = Result<T, AstroError>;
111
112impl AstroError {
113 pub fn invalid_date(year: i32, month: i32, day: i32, reason: &str) -> Self {
115 Self::InvalidDate {
116 year,
117 month,
118 day,
119 message: reason.to_string(),
120 }
121 }
122
123 pub fn math_error(operation: &str, kind: MathErrorKind, reason: &str) -> Self {
125 Self::MathError {
126 operation: operation.to_string(),
127 kind,
128 message: reason.to_string(),
129 }
130 }
131
132 pub fn external_library_error(function: &str, status_code: i32, message: &str) -> Self {
134 Self::ExternalLibraryError {
135 function: function.to_string(),
136 status_code,
137 message: message.to_string(),
138 }
139 }
140
141 pub fn data_error(file_type: &str, operation: &str, reason: &str) -> Self {
143 Self::DataError {
144 file_type: file_type.to_string(),
145 operation: operation.to_string(),
146 message: reason.to_string(),
147 }
148 }
149
150 pub fn calculation_error(context: &str, reason: &str) -> Self {
152 Self::CalculationError {
153 context: context.to_string(),
154 message: reason.to_string(),
155 }
156 }
157
158 pub fn is_recoverable(&self) -> bool {
162 match self {
163 Self::DataError { .. } => true,
164 Self::InvalidDate { .. } => false,
165 _ => false,
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn test_invalid_date_error() {
176 let err = AstroError::invalid_date(2000, 13, 1, "month out of range");
177 assert_eq!(
178 err.to_string(),
179 "Invalid date 2000-13-01: month out of range"
180 );
181 }
182
183 #[test]
184 fn test_math_error_with_kind() {
185 let err = AstroError::math_error(
186 "nanosecond addition",
187 MathErrorKind::Overflow,
188 "value too large",
189 );
190 assert!(err.to_string().contains("Math error"));
191 assert!(err.to_string().contains("Overflow"));
192 }
193
194 #[test]
195 fn test_external_library_error() {
196 let err = AstroError::external_library_error("telescope_driver", -2, "mount error");
197 assert!(err.to_string().contains("mount error"));
198 assert!(err.to_string().contains("telescope_driver"));
199 assert!(err.to_string().contains("status -2"));
200 }
201
202 #[test]
203 fn test_data_error() {
204 let err = AstroError::data_error("IERS Bulletin A", "download", "network timeout");
205 assert!(err
206 .to_string()
207 .contains("Data error (IERS Bulletin A - download)"));
208 }
209
210 #[test]
211 fn test_calculation_error() {
212 let err = AstroError::calculation_error("orbit propagation", "insufficient data");
213 assert!(err
214 .to_string()
215 .contains("Calculation error in orbit propagation"));
216 }
217
218 #[test]
219 fn test_recoverable_errors() {
220 assert!(AstroError::data_error("catalog", "download", "timeout").is_recoverable());
221 assert!(!AstroError::invalid_date(2000, 13, 1, "bad month").is_recoverable());
222 }
223
224 #[test]
225 fn test_send_sync() {
226 fn _assert_send<T: Send>() {}
227 fn _assert_sync<T: Sync>() {}
228 _assert_send::<AstroError>();
229 _assert_sync::<AstroError>();
230 }
231
232 #[test]
233 fn test_non_recoverable_errors() {
234 let math_err =
235 AstroError::math_error("calculation", MathErrorKind::Overflow, "value too large");
236 assert!(!math_err.is_recoverable());
237
238 let lib_err = AstroError::external_library_error("telescope_driver", -1, "mount error");
239 assert!(!lib_err.is_recoverable());
240
241 let calc_err = AstroError::calculation_error("orbit_propagation", "insufficient data");
242 assert!(!calc_err.is_recoverable());
243 }
244}