chess_vector_engine/
errors.rs

1use std::fmt;
2
3/// Custom error types for the chess engine with enhanced resilience support
4#[derive(Debug, Clone)]
5pub enum ChessEngineError {
6    /// Invalid chess position or move
7    InvalidPosition(String),
8    /// Database operation failed
9    DatabaseError(String),
10    /// Vector operation failed
11    VectorError(String),
12    /// Search operation failed or timed out
13    SearchError(String),
14    /// Neural network operation failed
15    NeuralNetworkError(String),
16    /// Training operation failed
17    TrainingError(String),
18    /// File I/O operation failed
19    IoError(String),
20    /// Configuration error
21    ConfigurationError(String),
22    /// Feature not available (e.g., GPU acceleration)
23    FeatureNotAvailable(String),
24    /// Resource exhausted (memory, time, etc.)
25    ResourceExhausted(String),
26    /// Operation failed after maximum retries
27    RetryExhausted {
28        operation: String,
29        attempts: u32,
30        last_error: String,
31    },
32    /// Timeout error with details
33    Timeout {
34        operation: String,
35        duration_ms: u64,
36    },
37    /// Validation error with context
38    ValidationError {
39        field: String,
40        value: String,
41        expected: String,
42    },
43    /// Chained error with context
44    ChainedError {
45        source: Box<ChessEngineError>,
46        context: String,
47    },
48    /// Circuit breaker is open
49    CircuitBreakerOpen {
50        operation: String,
51        failures: u32,
52    },
53    /// Memory limit exceeded
54    MemoryLimitExceeded {
55        requested_mb: usize,
56        available_mb: usize,
57        limit_mb: usize,
58    },
59}
60
61impl fmt::Display for ChessEngineError {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            ChessEngineError::InvalidPosition(msg) => write!(f, "Invalid position: {}", msg),
65            ChessEngineError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
66            ChessEngineError::VectorError(msg) => write!(f, "Vector operation error: {}", msg),
67            ChessEngineError::SearchError(msg) => write!(f, "Search error: {}", msg),
68            ChessEngineError::NeuralNetworkError(msg) => write!(f, "Neural network error: {}", msg),
69            ChessEngineError::TrainingError(msg) => write!(f, "Training error: {}", msg),
70            ChessEngineError::IoError(msg) => write!(f, "I/O error: {}", msg),
71            ChessEngineError::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg),
72            ChessEngineError::FeatureNotAvailable(msg) => {
73                write!(f, "Feature not available: {}", msg)
74            }
75            ChessEngineError::ResourceExhausted(msg) => write!(f, "Resource exhausted: {}", msg),
76            ChessEngineError::RetryExhausted { operation, attempts, last_error } => {
77                write!(f, "Operation '{}' failed after {} attempts: {}", operation, attempts, last_error)
78            }
79            ChessEngineError::Timeout { operation, duration_ms } => {
80                write!(f, "Operation '{}' timed out after {}ms", operation, duration_ms)
81            }
82            ChessEngineError::ValidationError { field, value, expected } => {
83                write!(f, "Validation failed for field '{}': got '{}', expected '{}'", field, value, expected)
84            }
85            ChessEngineError::ChainedError { source, context } => {
86                write!(f, "{}: {}", context, source)
87            }
88            ChessEngineError::CircuitBreakerOpen { operation, failures } => {
89                write!(f, "Circuit breaker open for '{}' after {} failures", operation, failures)
90            }
91            ChessEngineError::MemoryLimitExceeded { requested_mb, available_mb, limit_mb } => {
92                write!(f, "Memory limit exceeded: requested {}MB, available {}MB, limit {}MB", 
93                    requested_mb, available_mb, limit_mb)
94            }
95        }
96    }
97}
98
99impl std::error::Error for ChessEngineError {}
100
101// Convenience type alias
102pub type Result<T> = std::result::Result<T, ChessEngineError>;
103
104// Convert from common error types
105impl From<std::io::Error> for ChessEngineError {
106    fn from(error: std::io::Error) -> Self {
107        ChessEngineError::IoError(error.to_string())
108    }
109}
110
111impl From<serde_json::Error> for ChessEngineError {
112    fn from(error: serde_json::Error) -> Self {
113        ChessEngineError::IoError(format!("JSON serialization error: {}", error))
114    }
115}
116
117impl From<bincode::Error> for ChessEngineError {
118    fn from(error: bincode::Error) -> Self {
119        ChessEngineError::IoError(format!("Binary serialization error: {}", error))
120    }
121}
122
123#[cfg(feature = "database")]
124impl From<rusqlite::Error> for ChessEngineError {
125    fn from(error: rusqlite::Error) -> Self {
126        ChessEngineError::DatabaseError(error.to_string())
127    }
128}
129
130impl From<std::num::ParseIntError> for ChessEngineError {
131    fn from(error: std::num::ParseIntError) -> Self {
132        ChessEngineError::ValidationError {
133            field: "integer_parsing".to_string(),
134            value: "unknown".to_string(),
135            expected: format!("valid integer: {}", error),
136        }
137    }
138}
139
140impl From<std::num::ParseFloatError> for ChessEngineError {
141    fn from(error: std::num::ParseFloatError) -> Self {
142        ChessEngineError::ValidationError {
143            field: "float_parsing".to_string(),
144            value: "unknown".to_string(),
145            expected: format!("valid float: {}", error),
146        }
147    }
148}
149
150/// Enhanced error utilities for production resilience
151pub mod resilience {
152    use super::*;
153    use std::time::{Duration, Instant};
154    use std::thread;
155    
156    /// Configuration for retry operations
157    #[derive(Debug, Clone)]
158    pub struct RetryConfig {
159        pub max_attempts: u32,
160        pub initial_delay_ms: u64,
161        pub max_delay_ms: u64,
162        pub backoff_multiplier: f64,
163    }
164    
165    impl Default for RetryConfig {
166        fn default() -> Self {
167            Self {
168                max_attempts: 3,
169                initial_delay_ms: 100,
170                max_delay_ms: 5000,
171                backoff_multiplier: 2.0,
172            }
173        }
174    }
175    
176    /// Retry an operation with exponential backoff
177    pub fn retry_with_backoff<T, F, E>(
178        operation_name: &str,
179        config: &RetryConfig,
180        mut operation: F,
181    ) -> Result<T>
182    where
183        F: FnMut() -> std::result::Result<T, E>,
184        E: std::fmt::Display + Clone,
185    {
186        let mut last_error: Option<E> = None;
187        let mut delay_ms = config.initial_delay_ms;
188        
189        for attempt in 1..=config.max_attempts {
190            match operation() {
191                Ok(result) => return Ok(result),
192                Err(error) => {
193                    last_error = Some(error.clone());
194                    
195                    if attempt < config.max_attempts {
196                        thread::sleep(Duration::from_millis(delay_ms));
197                        delay_ms = ((delay_ms as f64) * config.backoff_multiplier) as u64;
198                        delay_ms = delay_ms.min(config.max_delay_ms);
199                    }
200                }
201            }
202        }
203        
204        Err(ChessEngineError::RetryExhausted {
205            operation: operation_name.to_string(),
206            attempts: config.max_attempts,
207            last_error: last_error.map(|e| e.to_string()).unwrap_or("unknown".to_string()),
208        })
209    }
210    
211    /// Circuit breaker state
212    #[derive(Debug, Clone, PartialEq)]
213    pub enum CircuitState {
214        Closed,
215        Open,
216        HalfOpen,
217    }
218    
219    /// Circuit breaker for failing operations
220    #[derive(Debug)]
221    pub struct CircuitBreaker {
222        pub state: CircuitState,
223        pub failure_count: u32,
224        pub failure_threshold: u32,
225        pub timeout_duration: Duration,
226        pub last_failure_time: Option<Instant>,
227    }
228    
229    impl CircuitBreaker {
230        pub fn new(failure_threshold: u32, timeout_duration: Duration) -> Self {
231            Self {
232                state: CircuitState::Closed,
233                failure_count: 0,
234                failure_threshold,
235                timeout_duration,
236                last_failure_time: None,
237            }
238        }
239        
240        pub fn call<T, F, E>(&mut self, operation_name: &str, operation: F) -> Result<T>
241        where
242            F: FnOnce() -> std::result::Result<T, E>,
243            E: std::fmt::Display,
244        {
245            match self.state {
246                CircuitState::Open => {
247                    if let Some(last_failure) = self.last_failure_time {
248                        if Instant::now().duration_since(last_failure) > self.timeout_duration {
249                            self.state = CircuitState::HalfOpen;
250                        } else {
251                            return Err(ChessEngineError::CircuitBreakerOpen {
252                                operation: operation_name.to_string(),
253                                failures: self.failure_count,
254                            });
255                        }
256                    }
257                }
258                _ => {}
259            }
260            
261            match operation() {
262                Ok(result) => {
263                    self.on_success();
264                    Ok(result)
265                }
266                Err(error) => {
267                    self.on_failure();
268                    Err(ChessEngineError::SearchError(format!(
269                        "Circuit breaker recorded failure in '{}': {}",
270                        operation_name, error
271                    )))
272                }
273            }
274        }
275        
276        fn on_success(&mut self) {
277            self.failure_count = 0;
278            self.state = CircuitState::Closed;
279        }
280        
281        fn on_failure(&mut self) {
282            self.failure_count += 1;
283            self.last_failure_time = Some(Instant::now());
284            
285            if self.failure_count >= self.failure_threshold {
286                self.state = CircuitState::Open;
287            }
288        }
289    }
290    
291    /// Memory monitor for resource management
292    #[derive(Debug)]
293    pub struct MemoryMonitor {
294        max_memory_mb: usize,
295        warning_threshold_mb: usize,
296    }
297    
298    impl MemoryMonitor {
299        pub fn new(max_memory_mb: usize) -> Self {
300            Self {
301                max_memory_mb,
302                warning_threshold_mb: (max_memory_mb as f64 * 0.8) as usize,
303            }
304        }
305        
306        pub fn check_allocation(&self, requested_bytes: usize) -> Result<()> {
307            let requested_mb = requested_bytes / (1024 * 1024);
308            
309            // Get current memory usage (simplified - in production you'd use a proper memory monitoring library)
310            let current_usage_mb = self.get_estimated_memory_usage();
311            
312            if current_usage_mb + requested_mb > self.max_memory_mb {
313                return Err(ChessEngineError::MemoryLimitExceeded {
314                    requested_mb,
315                    available_mb: self.max_memory_mb.saturating_sub(current_usage_mb),
316                    limit_mb: self.max_memory_mb,
317                });
318            }
319            
320            if current_usage_mb + requested_mb > self.warning_threshold_mb {
321                // Log warning but allow operation
322                eprintln!("Warning: Memory usage approaching limit: {}MB + {}MB > {}MB (warning threshold)",
323                    current_usage_mb, requested_mb, self.warning_threshold_mb);
324            }
325            
326            Ok(())
327        }
328        
329        fn get_estimated_memory_usage(&self) -> usize {
330            // Simplified memory estimation - in production use proper memory monitoring
331            // This is a placeholder that estimates based on process memory
332            64 // Return 64MB as a conservative estimate
333        }
334    }
335}
336
337// Helper macros for error creation
338#[macro_export]
339macro_rules! invalid_position {
340    ($msg:expr) => {
341        ChessEngineError::InvalidPosition($msg.to_string())
342    };
343    ($fmt:expr, $($arg:tt)*) => {
344        ChessEngineError::InvalidPosition(format!($fmt, $($arg)*))
345    };
346}
347
348#[macro_export]
349macro_rules! search_error {
350    ($msg:expr) => {
351        ChessEngineError::SearchError($msg.to_string())
352    };
353    ($fmt:expr, $($arg:tt)*) => {
354        ChessEngineError::SearchError(format!($fmt, $($arg)*))
355    };
356}
357
358#[macro_export]
359macro_rules! vector_error {
360    ($msg:expr) => {
361        ChessEngineError::VectorError($msg.to_string())
362    };
363    ($fmt:expr, $($arg:tt)*) => {
364        ChessEngineError::VectorError(format!($fmt, $($arg)*))
365    };
366}
367
368#[macro_export]
369macro_rules! training_error {
370    ($msg:expr) => {
371        ChessEngineError::TrainingError($msg.to_string())
372    };
373    ($fmt:expr, $($arg:tt)*) => {
374        ChessEngineError::TrainingError(format!($fmt, $($arg)*))
375    };
376}
377
378#[macro_export]
379macro_rules! config_error {
380    ($msg:expr) => {
381        ChessEngineError::ConfigurationError($msg.to_string())
382    };
383    ($fmt:expr, $($arg:tt)*) => {
384        ChessEngineError::ConfigurationError(format!($fmt, $($arg)*))
385    };
386}
387
388#[macro_export]
389macro_rules! resource_exhausted {
390    ($msg:expr) => {
391        ChessEngineError::ResourceExhausted($msg.to_string())
392    };
393    ($fmt:expr, $($arg:tt)*) => {
394        ChessEngineError::ResourceExhausted(format!($fmt, $($arg)*))
395    };
396}
397
398#[macro_export]
399macro_rules! validation_error {
400    ($field:expr, $value:expr, $expected:expr) => {
401        ChessEngineError::ValidationError {
402            field: $field.to_string(),
403            value: $value.to_string(),
404            expected: $expected.to_string(),
405        }
406    };
407}
408
409#[macro_export]
410macro_rules! add_context {
411    ($result:expr, $context:expr) => {
412        $result.map_err(|e| ChessEngineError::ChainedError {
413            source: Box::new(e),
414            context: $context.to_string(),
415        })
416    };
417}
418
419#[macro_export]
420macro_rules! memory_limit_exceeded {
421    ($requested:expr, $available:expr, $limit:expr) => {
422        ChessEngineError::MemoryLimitExceeded {
423            requested_mb: $requested,
424            available_mb: $available,
425            limit_mb: $limit,
426        }
427    };
428}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433
434    #[test]
435    fn test_error_display() {
436        let error = ChessEngineError::InvalidPosition("test position".to_string());
437        assert_eq!(error.to_string(), "Invalid position: test position");
438    }
439
440    #[test]
441    fn test_error_conversion() {
442        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
443        let chess_error: ChessEngineError = io_error.into();
444
445        match chess_error {
446            ChessEngineError::IoError(msg) => assert!(msg.contains("file not found")),
447            _ => panic!("Expected IoError"),
448        }
449    }
450
451    #[test]
452    fn test_error_macros() {
453        let error = invalid_position!(
454            "Invalid FEN: {}",
455            "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq"
456        );
457        match error {
458            ChessEngineError::InvalidPosition(msg) => assert!(msg.contains("Invalid FEN")),
459            _ => panic!("Expected InvalidPosition"),
460        }
461    }
462    
463    #[test]
464    fn test_enhanced_error_types() {
465        let validation_error = validation_error!("vector_size", "512", "1024");
466        match validation_error {
467            ChessEngineError::ValidationError { field, value, expected } => {
468                assert_eq!(field, "vector_size");
469                assert_eq!(value, "512");
470                assert_eq!(expected, "1024");
471            }
472            _ => panic!("Expected ValidationError"),
473        }
474        
475        let memory_error = ChessEngineError::MemoryLimitExceeded {
476            requested_mb: 1024,
477            available_mb: 512,
478            limit_mb: 1000,
479        };
480        match memory_error {
481            ChessEngineError::MemoryLimitExceeded { requested_mb, available_mb, limit_mb } => {
482                assert_eq!(requested_mb, 1024);
483                assert_eq!(available_mb, 512);
484                assert_eq!(limit_mb, 1000);
485            }
486            _ => panic!("Expected MemoryLimitExceeded"),
487        }
488    }
489    
490    #[test]
491    fn test_error_chaining() {
492        let base_error = search_error!("Base search failed");
493        let chained_result: Result<()> = Err(base_error);
494        let enhanced_result = add_context!(chained_result, "During similarity search operation");
495        
496        match enhanced_result {
497            Err(ChessEngineError::ChainedError { source, context }) => {
498                assert_eq!(context, "During similarity search operation");
499                match *source {
500                    ChessEngineError::SearchError(ref msg) => assert_eq!(msg, "Base search failed"),
501                    _ => panic!("Expected SearchError in chain"),
502                }
503            }
504            _ => panic!("Expected ChainedError"),
505        }
506    }
507}