1use std::fmt;
2
3#[derive(Debug, Clone)]
5pub enum ChessEngineError {
6 InvalidPosition(String),
8 DatabaseError(String),
10 VectorError(String),
12 SearchError(String),
14 NeuralNetworkError(String),
16 TrainingError(String),
18 IoError(String),
20 ConfigurationError(String),
22 FeatureNotAvailable(String),
24 ResourceExhausted(String),
26 RetryExhausted {
28 operation: String,
29 attempts: u32,
30 last_error: String,
31 },
32 Timeout {
34 operation: String,
35 duration_ms: u64,
36 },
37 ValidationError {
39 field: String,
40 value: String,
41 expected: String,
42 },
43 ChainedError {
45 source: Box<ChessEngineError>,
46 context: String,
47 },
48 CircuitBreakerOpen {
50 operation: String,
51 failures: u32,
52 },
53 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
101pub type Result<T> = std::result::Result<T, ChessEngineError>;
103
104impl 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
150pub mod resilience {
152 use super::*;
153 use std::time::{Duration, Instant};
154 use std::thread;
155
156 #[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 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 #[derive(Debug, Clone, PartialEq)]
213 pub enum CircuitState {
214 Closed,
215 Open,
216 HalfOpen,
217 }
218
219 #[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 #[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 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 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 64 }
334 }
335}
336
337#[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}