1use thiserror::Error;
8
9#[derive(Error, Debug)]
14pub enum TermError {
15 #[error("Validation failed: {message}")]
17 ValidationFailed {
18 message: String,
20 check: String,
22 #[source]
24 source: Option<Box<dyn std::error::Error + Send + Sync>>,
25 },
26
27 #[error("Constraint evaluation failed for '{constraint}': {message}")]
29 ConstraintEvaluation {
30 constraint: String,
32 message: String,
34 },
35
36 #[error("DataFusion error: {0}")]
38 DataFusion(#[from] datafusion::error::DataFusionError),
39
40 #[error("Arrow error: {0}")]
42 Arrow(#[from] arrow::error::ArrowError),
43
44 #[error("Data source error: {message}")]
46 DataSource {
47 source_type: String,
49 message: String,
51 #[source]
53 source: Option<Box<dyn std::error::Error + Send + Sync>>,
54 },
55
56 #[error("IO error: {0}")]
58 Io(#[from] std::io::Error),
59
60 #[error("Parse error: {0}")]
62 Parse(String),
63
64 #[error("Configuration error: {0}")]
66 Configuration(String),
67
68 #[error("Serialization error: {0}")]
70 Serialization(String),
71
72 #[error("OpenTelemetry error: {0}")]
74 OpenTelemetry(String),
75
76 #[error("Column '{column}' not found in dataset")]
78 ColumnNotFound { column: String },
79
80 #[error("Type mismatch: expected {expected}, found {found}")]
82 TypeMismatch { expected: String, found: String },
83
84 #[error("Operation not supported: {0}")]
86 NotSupported(String),
87
88 #[error("Internal error: {0}")]
90 Internal(String),
91
92 #[error("Security error: {0}")]
94 SecurityError(String),
95
96 #[error("Repository error ({operation} on {repository_type}): {message}")]
98 Repository {
99 repository_type: String,
101 operation: String,
103 message: String,
105 #[source]
107 source: Option<Box<dyn std::error::Error + Send + Sync>>,
108 },
109
110 #[error("Invalid repository key: {message}")]
112 InvalidRepositoryKey {
113 key: String,
115 message: String,
117 },
118
119 #[error("Invalid repository query: {message}")]
121 InvalidRepositoryQuery {
122 message: String,
124 query_info: String,
126 },
127
128 #[error("Repository key collision detected: {message}")]
130 RepositoryKeyCollision {
131 key: String,
133 message: String,
135 },
136
137 #[error("Repository validation error: {message}")]
139 RepositoryValidation {
140 field: String,
142 message: String,
144 invalid_value: String,
146 },
147}
148
149pub type Result<T> = std::result::Result<T, TermError>;
164
165impl TermError {
166 pub fn validation_failed(check: impl Into<String>, message: impl Into<String>) -> Self {
168 Self::ValidationFailed {
169 message: message.into(),
170 check: check.into(),
171 source: None,
172 }
173 }
174
175 pub fn validation_failed_with_source(
177 check: impl Into<String>,
178 message: impl Into<String>,
179 source: Box<dyn std::error::Error + Send + Sync>,
180 ) -> Self {
181 Self::ValidationFailed {
182 message: message.into(),
183 check: check.into(),
184 source: Some(source),
185 }
186 }
187
188 pub fn data_source(source_type: impl Into<String>, message: impl Into<String>) -> Self {
190 Self::DataSource {
191 source_type: source_type.into(),
192 message: message.into(),
193 source: None,
194 }
195 }
196
197 pub fn data_source_with_source(
199 source_type: impl Into<String>,
200 message: impl Into<String>,
201 source: Box<dyn std::error::Error + Send + Sync>,
202 ) -> Self {
203 Self::DataSource {
204 source_type: source_type.into(),
205 message: message.into(),
206 source: Some(source),
207 }
208 }
209
210 pub fn repository(
212 repository_type: impl Into<String>,
213 operation: impl Into<String>,
214 message: impl Into<String>,
215 ) -> Self {
216 Self::Repository {
217 repository_type: repository_type.into(),
218 operation: operation.into(),
219 message: message.into(),
220 source: None,
221 }
222 }
223
224 pub fn repository_with_source(
226 repository_type: impl Into<String>,
227 operation: impl Into<String>,
228 message: impl Into<String>,
229 source: Box<dyn std::error::Error + Send + Sync>,
230 ) -> Self {
231 Self::Repository {
232 repository_type: repository_type.into(),
233 operation: operation.into(),
234 message: message.into(),
235 source: Some(source),
236 }
237 }
238
239 pub fn invalid_repository_key(key: impl Into<String>, message: impl Into<String>) -> Self {
241 Self::InvalidRepositoryKey {
242 key: key.into(),
243 message: message.into(),
244 }
245 }
246
247 pub fn invalid_repository_query(
249 message: impl Into<String>,
250 query_info: impl Into<String>,
251 ) -> Self {
252 Self::InvalidRepositoryQuery {
253 message: message.into(),
254 query_info: query_info.into(),
255 }
256 }
257
258 pub fn repository_key_collision(key: impl Into<String>, message: impl Into<String>) -> Self {
260 Self::RepositoryKeyCollision {
261 key: key.into(),
262 message: message.into(),
263 }
264 }
265
266 pub fn repository_validation(
268 field: impl Into<String>,
269 message: impl Into<String>,
270 invalid_value: impl Into<String>,
271 ) -> Self {
272 Self::RepositoryValidation {
273 field: field.into(),
274 message: message.into(),
275 invalid_value: invalid_value.into(),
276 }
277 }
278
279 pub fn constraint_evaluation(
281 constraint: impl Into<String>,
282 message: impl Into<String>,
283 ) -> Self {
284 Self::ConstraintEvaluation {
285 constraint: constraint.into(),
286 message: message.into(),
287 }
288 }
289}
290
291impl From<crate::analyzers::AnalyzerError> for TermError {
293 fn from(err: crate::analyzers::AnalyzerError) -> Self {
294 use crate::analyzers::AnalyzerError;
295
296 match err {
297 AnalyzerError::StateComputation(msg) => {
298 TermError::Internal(format!("Analyzer state computation failed: {msg}"))
299 }
300 AnalyzerError::MetricComputation(msg) => {
301 TermError::Internal(format!("Analyzer metric computation failed: {msg}"))
302 }
303 AnalyzerError::StateMerge(msg) => {
304 TermError::Internal(format!("Analyzer state merge failed: {msg}"))
305 }
306 AnalyzerError::QueryExecution(e) => TermError::DataFusion(e),
307 AnalyzerError::ArrowComputation(e) => TermError::Arrow(e),
308 AnalyzerError::InvalidConfiguration(msg) => TermError::Configuration(msg),
309 AnalyzerError::InvalidData(msg) => TermError::Parse(msg),
310 AnalyzerError::NoData => {
311 TermError::Internal("No data available for analysis".to_string())
312 }
313 AnalyzerError::Serialization(msg) => TermError::Serialization(msg),
314 AnalyzerError::Custom(msg) => TermError::Internal(msg),
315 }
316 }
317}
318
319pub trait ErrorContext<T> {
321 fn context(self, msg: &str) -> Result<T>;
323
324 fn with_context<F>(self, f: F) -> Result<T>
326 where
327 F: FnOnce() -> String;
328}
329
330impl<T, E> ErrorContext<T> for std::result::Result<T, E>
331where
332 E: Into<TermError>,
333{
334 fn context(self, msg: &str) -> Result<T> {
335 self.map_err(|e| {
336 let base_error = e.into();
337 match base_error {
338 TermError::Internal(inner) => TermError::Internal(format!("{msg}: {inner}")),
339 other => TermError::Internal(format!("{msg}: {other}")),
340 }
341 })
342 }
343
344 fn with_context<F>(self, f: F) -> Result<T>
345 where
346 F: FnOnce() -> String,
347 {
348 self.map_err(|e| {
349 let msg = f();
350 let base_error = e.into();
351 match base_error {
352 TermError::Internal(inner) => TermError::Internal(format!("{msg}: {inner}")),
353 other => TermError::Internal(format!("{msg}: {other}")),
354 }
355 })
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use std::error::Error;
363
364 #[test]
365 fn test_validation_failed_error() {
366 let err = TermError::validation_failed("completeness_check", "Too many null values");
367 assert_eq!(err.to_string(), "Validation failed: Too many null values");
368 }
369
370 #[test]
371 fn test_error_with_source() {
372 let source = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
373 let err = TermError::validation_failed_with_source(
374 "file_check",
375 "Could not read validation file",
376 Box::new(source),
377 );
378
379 assert!(err.source().is_some());
381 }
382
383 #[test]
384 fn test_data_source_error() {
385 let err = TermError::data_source("CSV", "Invalid file format");
386 assert_eq!(err.to_string(), "Data source error: Invalid file format");
387 }
388
389 #[test]
390 fn test_column_not_found() {
391 let err = TermError::ColumnNotFound {
392 column: "user_id".to_string(),
393 };
394 assert_eq!(err.to_string(), "Column 'user_id' not found in dataset");
395 }
396
397 #[test]
398 fn test_type_mismatch() {
399 let err = TermError::TypeMismatch {
400 expected: "Int64".to_string(),
401 found: "Utf8".to_string(),
402 };
403 assert_eq!(err.to_string(), "Type mismatch: expected Int64, found Utf8");
404 }
405
406 #[test]
407 fn test_error_context() {
408 fn failing_operation() -> Result<()> {
409 Err(TermError::Internal("Something went wrong".to_string()))
410 }
411
412 let result = failing_operation().context("During data validation");
413 assert!(result.is_err());
414 let err = result.unwrap_err();
415 assert!(err.to_string().contains("During data validation"));
416 }
417}