lethe_core_rust/
error.rs

1use thiserror::Error;
2
3/// Main error type for the Lethe system
4#[derive(Error, Debug)]
5pub enum LetheError {
6    /// Database-related errors
7    #[error("Database error: {message}")]
8    Database { message: String },
9
10    /// Embedding service errors
11    #[error("Embedding error: {message}")]
12    Embedding { message: String },
13
14    /// Configuration errors
15    #[error("Configuration error: {message}")]
16    Config { message: String },
17
18    /// Validation errors
19    #[error("Validation error in {field}: {reason}")]
20    Validation { field: String, reason: String },
21
22    /// IO errors
23    #[error("IO error: {0}")]
24    Io(#[from] std::io::Error),
25
26    /// JSON Serialization errors
27    #[error("JSON serialization error: {0}")]
28    JsonSerialization(#[from] serde_json::Error),
29
30    /// YAML Serialization errors
31    #[error("YAML serialization error: {0}")]
32    YamlSerialization(#[from] serde_yaml::Error),
33
34    /// HTTP client errors
35    #[cfg(feature = "ollama")]
36    #[error("HTTP client error: {0}")]
37    Http(#[from] reqwest::Error),
38
39    /// Timeout errors
40    #[error("Operation timed out: {operation} after {timeout_ms}ms")]
41    Timeout { operation: String, timeout_ms: u64 },
42
43    /// Resource not found
44    #[error("Resource not found: {resource_type} with id {id}")]
45    NotFound { resource_type: String, id: String },
46
47    /// Authentication errors
48    #[error("Authentication failed: {message}")]
49    Authentication { message: String },
50
51    /// Authorization errors
52    #[error("Authorization failed: {message}")]
53    Authorization { message: String },
54
55    /// External service errors
56    #[error("External service error: {service} - {message}")]
57    ExternalService { service: String, message: String },
58
59    /// Processing pipeline errors
60    #[error("Pipeline error: {stage} - {message}")]
61    Pipeline { stage: String, message: String },
62
63    /// Vector operations errors
64    #[error("Vector operation error: {message}")]
65    Vector { message: String },
66
67    /// Mathematical optimization errors
68    #[error("Mathematical optimization error: {message}")]
69    MathOptimization { message: String },
70
71    /// Generic internal errors
72    #[error("Internal error: {message}")]
73    Internal { message: String },
74}
75
76/// Result type alias for Lethe operations
77pub type Result<T> = std::result::Result<T, LetheError>;
78
79impl LetheError {
80    /// Create a database error
81    pub fn database(message: impl Into<String>) -> Self {
82        Self::Database {
83            message: message.into(),
84        }
85    }
86
87    /// Create an embedding error
88    pub fn embedding(message: impl Into<String>) -> Self {
89        Self::Embedding {
90            message: message.into(),
91        }
92    }
93
94    /// Create a configuration error
95    pub fn config(message: impl Into<String>) -> Self {
96        Self::Config {
97            message: message.into(),
98        }
99    }
100
101    /// Create a validation error
102    pub fn validation(field: impl Into<String>, reason: impl Into<String>) -> Self {
103        Self::Validation {
104            field: field.into(),
105            reason: reason.into(),
106        }
107    }
108
109    /// Create a timeout error
110    pub fn timeout(operation: impl Into<String>, timeout_ms: u64) -> Self {
111        Self::Timeout {
112            operation: operation.into(),
113            timeout_ms,
114        }
115    }
116
117    /// Create a not found error
118    pub fn not_found(resource_type: impl Into<String>, id: impl Into<String>) -> Self {
119        Self::NotFound {
120            resource_type: resource_type.into(),
121            id: id.into(),
122        }
123    }
124
125    /// Create an external service error
126    pub fn external_service(service: impl Into<String>, message: impl Into<String>) -> Self {
127        Self::ExternalService {
128            service: service.into(),
129            message: message.into(),
130        }
131    }
132
133    /// Create a pipeline error
134    pub fn pipeline(stage: impl Into<String>, message: impl Into<String>) -> Self {
135        Self::Pipeline {
136            stage: stage.into(),
137            message: message.into(),
138        }
139    }
140
141    /// Create a vector operation error
142    pub fn vector(message: impl Into<String>) -> Self {
143        Self::Vector {
144            message: message.into(),
145        }
146    }
147
148    /// Create an internal error
149    pub fn internal(message: impl Into<String>) -> Self {
150        Self::Internal {
151            message: message.into(),
152        }
153    }
154}
155
156#[cfg(feature = "database")]
157impl From<sqlx::Error> for LetheError {
158    fn from(err: sqlx::Error) -> Self {
159        Self::database(err.to_string())
160    }
161}
162
163impl From<validator::ValidationErrors> for LetheError {
164    fn from(err: validator::ValidationErrors) -> Self {
165        Self::validation("validation", err.to_string())
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use std::io;
173
174    // Test basic error creation functions
175    #[test]
176    fn test_database_error() {
177        let err = LetheError::database("Connection failed");
178        assert!(matches!(err, LetheError::Database { .. }));
179        assert_eq!(err.to_string(), "Database error: Connection failed");
180    }
181
182    #[test]
183    fn test_embedding_error() {
184        let err = LetheError::embedding("Model not available");
185        assert!(matches!(err, LetheError::Embedding { .. }));
186        assert_eq!(err.to_string(), "Embedding error: Model not available");
187    }
188
189    #[test]
190    fn test_config_error() {
191        let err = LetheError::config("Invalid configuration");
192        assert!(matches!(err, LetheError::Config { .. }));
193        assert_eq!(err.to_string(), "Configuration error: Invalid configuration");
194    }
195
196    #[test]
197    fn test_validation_error() {
198        let err = LetheError::validation("email", "Invalid format");
199        assert!(matches!(err, LetheError::Validation { .. }));
200        assert_eq!(err.to_string(), "Validation error in email: Invalid format");
201    }
202
203    #[test]
204    fn test_timeout_error() {
205        let err = LetheError::timeout("database_query", 5000);
206        assert!(matches!(err, LetheError::Timeout { .. }));
207        assert_eq!(err.to_string(), "Operation timed out: database_query after 5000ms");
208    }
209
210    #[test]
211    fn test_not_found_error() {
212        let err = LetheError::not_found("user", "123");
213        assert!(matches!(err, LetheError::NotFound { .. }));
214        assert_eq!(err.to_string(), "Resource not found: user with id 123");
215    }
216
217    #[test]
218    fn test_external_service_error() {
219        let err = LetheError::external_service("openai", "API rate limit exceeded");
220        assert!(matches!(err, LetheError::ExternalService { .. }));
221        assert_eq!(err.to_string(), "External service error: openai - API rate limit exceeded");
222    }
223
224    #[test]
225    fn test_pipeline_error() {
226        let err = LetheError::pipeline("embedding", "Failed to encode text");
227        assert!(matches!(err, LetheError::Pipeline { .. }));
228        assert_eq!(err.to_string(), "Pipeline error: embedding - Failed to encode text");
229    }
230
231    #[test]
232    fn test_vector_error() {
233        let err = LetheError::vector("Dimension mismatch");
234        assert!(matches!(err, LetheError::Vector { .. }));
235        assert_eq!(err.to_string(), "Vector operation error: Dimension mismatch");
236    }
237
238    #[test]
239    fn test_internal_error() {
240        let err = LetheError::internal("Unexpected state");
241        assert!(matches!(err, LetheError::Internal { .. }));
242        assert_eq!(err.to_string(), "Internal error: Unexpected state");
243    }
244
245    // Test error variant matching and properties
246    #[test]
247    fn test_authentication_error_variant() {
248        let err = LetheError::Authentication { 
249            message: "Invalid token".to_string() 
250        };
251        assert!(matches!(err, LetheError::Authentication { .. }));
252        assert_eq!(err.to_string(), "Authentication failed: Invalid token");
253    }
254
255    #[test]
256    fn test_authorization_error_variant() {
257        let err = LetheError::Authorization { 
258            message: "Insufficient permissions".to_string() 
259        };
260        assert!(matches!(err, LetheError::Authorization { .. }));
261        assert_eq!(err.to_string(), "Authorization failed: Insufficient permissions");
262    }
263
264    #[test]
265    fn test_math_optimization_error_variant() {
266        let err = LetheError::MathOptimization { 
267            message: "Convergence failed".to_string() 
268        };
269        assert!(matches!(err, LetheError::MathOptimization { .. }));
270        assert_eq!(err.to_string(), "Mathematical optimization error: Convergence failed");
271    }
272
273    // Test automatic conversions (From implementations)
274    #[test]
275    fn test_from_io_error() {
276        let io_err = io::Error::new(io::ErrorKind::NotFound, "File not found");
277        let lethe_err: LetheError = io_err.into();
278        
279        assert!(matches!(lethe_err, LetheError::Io(_)));
280        assert!(lethe_err.to_string().contains("File not found"));
281    }
282
283    #[test]
284    fn test_from_serde_json_error() {
285        let json_err = serde_json::from_str::<serde_json::Value>("invalid json")
286            .unwrap_err();
287        let lethe_err: LetheError = json_err.into();
288        
289        assert!(matches!(lethe_err, LetheError::JsonSerialization(_)));
290        assert!(lethe_err.to_string().contains("JSON serialization error"));
291    }
292
293    #[test]
294    fn test_from_reqwest_error() {
295        // Create a mock reqwest error by trying to parse an invalid URL
296        let req_err = reqwest::Client::new()
297            .get("not-a-valid-url")
298            .build()
299            .unwrap_err();
300        let lethe_err: LetheError = req_err.into();
301        
302        assert!(matches!(lethe_err, LetheError::Http(_)));
303        assert!(lethe_err.to_string().contains("HTTP client error"));
304    }
305
306    // Test that Result type works correctly
307    #[test]
308    fn test_result_type_ok() {
309        let result: Result<i32> = Ok(42);
310        assert!(result.is_ok());
311        assert_eq!(result.unwrap(), 42);
312    }
313
314    #[test]
315    fn test_result_type_err() {
316        let result: Result<i32> = Err(LetheError::internal("Test error"));
317        assert!(result.is_err());
318        assert!(matches!(result.unwrap_err(), LetheError::Internal { .. }));
319    }
320
321    // Test error chaining and context preservation
322    #[test]
323    fn test_error_chain_io() {
324        fn inner_function() -> std::io::Result<String> {
325            Err(io::Error::new(io::ErrorKind::PermissionDenied, "Access denied"))
326        }
327        
328        fn outer_function() -> Result<String> {
329            let content = inner_function()?; // Automatic conversion
330            Ok(content)
331        }
332        
333        let result = outer_function();
334        assert!(result.is_err());
335        
336        let err = result.unwrap_err();
337        assert!(matches!(err, LetheError::Io(_)));
338        assert!(err.to_string().contains("Access denied"));
339    }
340
341    #[test]
342    fn test_error_chain_serialization() {
343        fn deserialize_config() -> Result<serde_json::Value> {
344            let config: serde_json::Value = serde_json::from_str("{invalid}")?;
345            Ok(config)
346        }
347        
348        let result = deserialize_config();
349        assert!(result.is_err());
350        
351        let err = result.unwrap_err();
352        assert!(matches!(err, LetheError::JsonSerialization(_)));
353    }
354
355    // Test error formatting and display
356    #[test]
357    fn test_error_debug_format() {
358        let err = LetheError::validation("field", "reason");
359        let debug_str = format!("{:?}", err);
360        assert!(debug_str.contains("Validation"));
361        assert!(debug_str.contains("field"));
362        assert!(debug_str.contains("reason"));
363    }
364
365    #[test]
366    fn test_error_display_format() {
367        let err = LetheError::database("Connection timeout");
368        let display_str = format!("{}", err);
369        assert_eq!(display_str, "Database error: Connection timeout");
370    }
371
372    // Test error equality and comparison (Debug trait)
373    #[test]
374    fn test_error_variants_are_different() {
375        let err1 = LetheError::database("message");
376        let err2 = LetheError::embedding("message");
377        
378        // They should have different debug representations
379        assert_ne!(format!("{:?}", err1), format!("{:?}", err2));
380    }
381
382    // Test error context preservation in complex scenarios
383    #[test]
384    fn test_complex_error_scenario() {
385        fn process_user_data(data: &str) -> Result<String> {
386            // Simulate validation error
387            if data.is_empty() {
388                return Err(LetheError::validation("data", "Cannot be empty"));
389            }
390            
391            // Simulate serialization error
392            let parsed: serde_json::Value = serde_json::from_str(data)?;
393            
394            // Simulate business logic error
395            if !parsed.is_object() {
396                return Err(LetheError::pipeline("validation", "Data must be an object"));
397            }
398            
399            Ok("processed".to_string())
400        }
401        
402        // Test empty data
403        let result1 = process_user_data("");
404        assert!(result1.is_err());
405        assert!(matches!(result1.unwrap_err(), LetheError::Validation { .. }));
406        
407        // Test invalid JSON
408        let result2 = process_user_data("invalid json");
409        assert!(result2.is_err());
410        assert!(matches!(result2.unwrap_err(), LetheError::JsonSerialization(_)));
411        
412        // Test non-object JSON
413        let result3 = process_user_data("\"string\"");
414        assert!(result3.is_err());
415        assert!(matches!(result3.unwrap_err(), LetheError::Pipeline { .. }));
416        
417        // Test valid data
418        let result4 = process_user_data("{\"key\": \"value\"}");
419        assert!(result4.is_ok());
420        assert_eq!(result4.unwrap(), "processed");
421    }
422
423    // Test error conversion from validator crate
424    #[test]
425    fn test_from_validator_errors() {
426        use validator::{Validate, ValidationErrors, ValidationError};
427        
428        #[derive(Validate)]
429        struct TestStruct {
430            #[validate(length(min = 1))]
431            name: String,
432        }
433        
434        let test_struct = TestStruct {
435            name: "".to_string(), // Invalid: too short
436        };
437        
438        let validation_result = test_struct.validate();
439        assert!(validation_result.is_err());
440        
441        let validation_errors = validation_result.unwrap_err();
442        let lethe_error: LetheError = validation_errors.into();
443        
444        assert!(matches!(lethe_error, LetheError::Validation { .. }));
445        assert!(lethe_error.to_string().contains("Validation error"));
446    }
447
448    // Test SQL error conversion (requires sqlx feature)
449    #[cfg(feature = "database")]
450    #[test]
451    fn test_from_sqlx_error() {
452        // Create a mock database URL parsing error
453        let db_error = sqlx::Error::Configuration("Invalid database URL".into());
454        let lethe_error: LetheError = db_error.into();
455        
456        assert!(matches!(lethe_error, LetheError::Database { .. }));
457        assert!(lethe_error.to_string().contains("Database error"));
458        assert!(lethe_error.to_string().contains("Invalid database URL"));
459    }
460
461    // Test error source and cause chain
462    #[test]
463    fn test_error_source_chain() {
464        let io_err = io::Error::new(io::ErrorKind::NotFound, "Original cause");
465        let lethe_err: LetheError = io_err.into();
466        
467        // The source should be preserved
468        let source = std::error::Error::source(&lethe_err);
469        assert!(source.is_some());
470        assert_eq!(source.unwrap().to_string(), "Original cause");
471    }
472
473    // Test all error variants are constructible
474    #[test]
475    fn test_all_error_variants() {
476        let errors = vec![
477            LetheError::Database { message: "db".to_string() },
478            LetheError::Embedding { message: "embed".to_string() },
479            LetheError::Config { message: "config".to_string() },
480            LetheError::Validation { field: "field".to_string(), reason: "reason".to_string() },
481            LetheError::Io(io::Error::new(io::ErrorKind::Other, "io")),
482            LetheError::JsonSerialization(serde_json::from_str::<()>("invalid").unwrap_err()),
483            LetheError::Http(reqwest::Client::new().get("invalid-url").build().unwrap_err()),
484            LetheError::Timeout { operation: "op".to_string(), timeout_ms: 1000 },
485            LetheError::NotFound { resource_type: "user".to_string(), id: "123".to_string() },
486            LetheError::Authentication { message: "auth".to_string() },
487            LetheError::Authorization { message: "authz".to_string() },
488            LetheError::ExternalService { service: "svc".to_string(), message: "msg".to_string() },
489            LetheError::Pipeline { stage: "stage".to_string(), message: "msg".to_string() },
490            LetheError::Vector { message: "vector".to_string() },
491            LetheError::MathOptimization { message: "math".to_string() },
492            LetheError::Internal { message: "internal".to_string() },
493        ];
494        
495        // All errors should be displayable and debuggable
496        for err in errors {
497            let _ = format!("{}", err);
498            let _ = format!("{:?}", err);
499        }
500    }
501
502    // Test error helper functions with different input types
503    #[test]
504    fn test_helper_functions_with_different_inputs() {
505        // String literals
506        let err1 = LetheError::database("literal");
507        assert_eq!(err1.to_string(), "Database error: literal");
508        
509        // String objects
510        let msg = "owned string".to_string();
511        let err2 = LetheError::embedding(&msg);
512        assert_eq!(err2.to_string(), "Embedding error: owned string");
513        
514        // String slices
515        let slice = "slice";
516        let err3 = LetheError::config(slice);
517        assert_eq!(err3.to_string(), "Configuration error: slice");
518        
519        // Formatted strings
520        let err4 = LetheError::validation("field", format!("error {}", 42));
521        assert_eq!(err4.to_string(), "Validation error in field: error 42");
522    }
523
524    // Test error size and memory efficiency
525    #[test]
526    fn test_error_size_reasonable() {
527        use std::mem;
528        
529        // Error should not be too large in memory
530        let size = mem::size_of::<LetheError>();
531        
532        // This is a rough check - errors shouldn't be huge
533        // Most variants should fit in a reasonable size
534        assert!(size < 500, "LetheError size is {} bytes, might be too large", size);
535    }
536
537    // Test error in Result context with ? operator
538    #[test]
539    fn test_question_mark_operator() {
540        fn function_that_fails() -> Result<i32> {
541            let _value: serde_json::Value = serde_json::from_str("invalid")?;
542            Ok(42)
543        }
544        
545        let result = function_that_fails();
546        assert!(result.is_err());
547        assert!(matches!(result.unwrap_err(), LetheError::JsonSerialization(_)));
548    }
549
550    // Test error downcasting (checking underlying error types)
551    #[test]
552    fn test_error_downcast_patterns() {
553        let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "Permission denied");
554        let lethe_err: LetheError = io_err.into();
555        
556        match &lethe_err {
557            LetheError::Io(inner_err) => {
558                assert_eq!(inner_err.kind(), io::ErrorKind::PermissionDenied);
559                assert_eq!(inner_err.to_string(), "Permission denied");
560            },
561            _ => panic!("Expected Io variant"),
562        }
563    }
564
565    // Integration test: Error propagation through multiple layers
566    #[test]
567    fn test_multi_layer_error_propagation() {
568        fn data_layer() -> std::io::Result<String> {
569            Err(io::Error::new(io::ErrorKind::NotFound, "Data file not found"))
570        }
571        
572        fn business_layer() -> Result<String> {
573            let data = data_layer()?; // io::Error -> LetheError::Io
574            Ok(data.to_uppercase())
575        }
576        
577        fn api_layer() -> Result<String> {
578            let processed = business_layer()?; // Propagates LetheError
579            Ok(format!("Response: {}", processed))
580        }
581        
582        let result = api_layer();
583        assert!(result.is_err());
584        
585        let error = result.unwrap_err();
586        assert!(matches!(error, LetheError::Io(_)));
587        assert!(error.to_string().contains("Data file not found"));
588    }
589
590    // Test custom error message formatting
591    #[test]
592    fn test_custom_error_formatting() {
593        let err = LetheError::ExternalService {
594            service: "OpenAI".to_string(),
595            message: "Rate limit exceeded: 1000 requests/min".to_string(),
596        };
597        
598        let formatted = err.to_string();
599        assert!(formatted.contains("External service error"));
600        assert!(formatted.contains("OpenAI"));
601        assert!(formatted.contains("Rate limit exceeded"));
602    }
603}