1use crate::{NetworkError, TrainingError};
7use std::error::Error;
8use thiserror::Error;
9
10#[derive(Error, Debug)]
12pub enum RuvFannError {
13 #[error("Network error: {category:?} - {message}")]
15 Network {
16 category: NetworkErrorCategory,
17 message: String,
18 context: Option<String>,
19 },
20
21 #[error("Training error: {category:?} - {message}")]
23 Training {
24 category: TrainingErrorCategory,
25 message: String,
26 context: Option<String>,
27 },
28
29 #[error("Cascade error: {category:?} - {message}")]
31 Cascade {
32 category: CascadeErrorCategory,
33 message: String,
34 context: Option<String>,
35 },
36
37 #[error("Validation error: {category:?} - {message}")]
39 Validation {
40 category: ValidationErrorCategory,
41 message: String,
42 details: Vec<String>,
43 },
44
45 #[error("I/O error: {category:?} - {message}")]
47 Io {
48 category: IoErrorCategory,
49 message: String,
50 source: Option<Box<dyn Error + Send + Sync>>,
51 },
52
53 #[error("Parallel processing error: {message}")]
55 Parallel {
56 message: String,
57 thread_count: usize,
58 context: Option<String>,
59 },
60
61 #[error("Memory error: {message}")]
63 Memory {
64 message: String,
65 requested_bytes: Option<usize>,
66 available_bytes: Option<usize>,
67 },
68
69 #[error("Performance error: {message}")]
71 Performance {
72 message: String,
73 metric: String,
74 threshold: f64,
75 actual: f64,
76 },
77
78 #[error("FANN compatibility error: {message}")]
80 Compatibility {
81 message: String,
82 fann_version: Option<String>,
83 operation: String,
84 },
85}
86
87#[derive(Debug, Clone, PartialEq)]
89pub enum NetworkErrorCategory {
90 Topology,
92 Weights,
94 Layers,
96 Connections,
98 Activation,
100 Propagation,
102}
103
104#[derive(Debug, Clone, PartialEq)]
106pub enum TrainingErrorCategory {
107 Algorithm,
109 Convergence,
111 Gradients,
113 LearningRate,
115 Iteration,
117 StopCriteria,
119}
120
121#[derive(Debug, Clone, PartialEq)]
123pub enum CascadeErrorCategory {
124 CandidateGeneration,
126 CandidateTraining,
128 CandidateSelection,
130 TopologyModification,
132 CorrelationCalculation,
134 OutputTraining,
136}
137
138#[derive(Debug, Clone, PartialEq)]
140pub enum ValidationErrorCategory {
141 InputData,
143 OutputData,
145 NetworkConfig,
147 TrainingParams,
149 CascadeParams,
151}
152
153#[derive(Debug, Clone, PartialEq)]
155pub enum IoErrorCategory {
156 FileAccess,
158 Serialization,
160 Format,
162 NetworkIo,
164 DataIo,
166}
167
168#[derive(Debug, Clone, PartialEq)]
170pub enum ErrorCategory {
171 Network(NetworkErrorCategory),
172 Training(TrainingErrorCategory),
173 Cascade(CascadeErrorCategory),
174 Validation(ValidationErrorCategory),
175 Io(IoErrorCategory),
176 Parallel,
177 Memory,
178 Performance,
179 Compatibility,
180}
181
182#[derive(Error, Debug)]
184pub enum ValidationError {
185 #[error("Parameter out of range: {parameter} = {value}, expected {min} <= value <= {max}")]
186 OutOfRange {
187 parameter: String,
188 value: f64,
189 min: f64,
190 max: f64,
191 },
192
193 #[error("Invalid configuration: {message}")]
194 InvalidConfig { message: String },
195
196 #[error("Missing required parameter: {parameter}")]
197 MissingParameter { parameter: String },
198
199 #[error("Incompatible parameters: {message}")]
200 IncompatibleParams { message: String },
201
202 #[error("Data format error: {message}")]
203 DataFormat { message: String },
204}
205
206#[derive(Debug, Clone)]
208pub struct ErrorContext {
209 pub operation: String,
210 pub network_id: Option<String>,
211 pub layer_index: Option<usize>,
212 pub neuron_index: Option<usize>,
213 pub epoch: Option<usize>,
214 pub timestamp: std::time::SystemTime,
215 pub additional_info: std::collections::HashMap<String, String>,
216}
217
218impl ErrorContext {
219 pub fn new(operation: impl Into<String>) -> Self {
220 Self {
221 operation: operation.into(),
222 network_id: None,
223 layer_index: None,
224 neuron_index: None,
225 epoch: None,
226 timestamp: std::time::SystemTime::now(),
227 additional_info: std::collections::HashMap::new(),
228 }
229 }
230
231 pub fn with_network_id(mut self, id: impl Into<String>) -> Self {
232 self.network_id = Some(id.into());
233 self
234 }
235
236 pub fn with_layer(mut self, index: usize) -> Self {
237 self.layer_index = Some(index);
238 self
239 }
240
241 pub fn with_neuron(mut self, index: usize) -> Self {
242 self.neuron_index = Some(index);
243 self
244 }
245
246 pub fn with_epoch(mut self, epoch: usize) -> Self {
247 self.epoch = Some(epoch);
248 self
249 }
250
251 pub fn with_info(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
252 self.additional_info.insert(key.into(), value.into());
253 self
254 }
255}
256
257#[derive(Debug, Clone)]
259pub enum RecoveryStrategy {
260 Retry,
262 RetryWithModification(std::collections::HashMap<String, String>),
264 Reset,
266 Skip,
268 Abort,
270 Fallback(String),
272}
273
274#[derive(Debug)]
276pub struct RecoveryContext {
277 pub strategy: RecoveryStrategy,
278 pub max_retries: usize,
279 pub current_retry: usize,
280 pub fallback_available: bool,
281 pub checkpoints: Vec<String>,
282}
283
284impl RecoveryContext {
285 pub fn new(strategy: RecoveryStrategy) -> Self {
286 Self {
287 strategy,
288 max_retries: 3,
289 current_retry: 0,
290 fallback_available: false,
291 checkpoints: Vec::new(),
292 }
293 }
294
295 pub fn should_retry(&self) -> bool {
296 self.current_retry < self.max_retries
297 }
298
299 pub fn increment_retry(&mut self) {
300 self.current_retry += 1;
301 }
302
303 pub fn reset_retry_count(&mut self) {
304 self.current_retry = 0;
305 }
306}
307
308pub struct ErrorLogger {
310 #[cfg(feature = "logging")]
311 log_level: log::Level,
312 #[cfg(not(feature = "logging"))]
313 log_level: u8, structured_logging: bool,
315 performance_tracking: bool,
316}
317
318impl ErrorLogger {
319 pub fn new() -> Self {
320 Self {
321 #[cfg(feature = "logging")]
322 log_level: log::Level::Warn,
323 #[cfg(not(feature = "logging"))]
324 log_level: 2, structured_logging: true,
326 performance_tracking: false,
327 }
328 }
329
330 #[cfg(feature = "logging")]
331 pub fn with_level(mut self, level: log::Level) -> Self {
332 self.log_level = level;
333 self
334 }
335
336 #[cfg(not(feature = "logging"))]
337 pub fn with_level(self, _level: u8) -> Self {
338 self
340 }
341
342 pub fn with_structured_logging(mut self, enabled: bool) -> Self {
343 self.structured_logging = enabled;
344 self
345 }
346
347 pub fn with_performance_tracking(mut self, enabled: bool) -> Self {
348 self.performance_tracking = enabled;
349 self
350 }
351
352 pub fn log_error(&self, error: &RuvFannError, context: Option<&ErrorContext>) {
353 if self.structured_logging {
354 self.log_structured_error(error, context);
355 } else {
356 self.log_simple_error(error, context);
357 }
358 }
359
360 fn log_structured_error(&self, error: &RuvFannError, context: Option<&ErrorContext>) {
361 #[cfg(feature = "serde")]
362 {
363 let mut fields = serde_json::Map::new();
364 fields.insert(
365 "error_type".to_string(),
366 serde_json::Value::String(format!("{error:?}")),
367 );
368 fields.insert(
369 "message".to_string(),
370 serde_json::Value::String(error.to_string()),
371 );
372
373 if let Some(ctx) = context {
374 fields.insert(
375 "operation".to_string(),
376 serde_json::Value::String(ctx.operation.clone()),
377 );
378 if let Some(ref network_id) = ctx.network_id {
379 fields.insert(
380 "network_id".to_string(),
381 serde_json::Value::String(network_id.clone()),
382 );
383 }
384 if let Some(layer_idx) = ctx.layer_index {
385 fields.insert(
386 "layer_index".to_string(),
387 serde_json::Value::Number(serde_json::Number::from(layer_idx)),
388 );
389 }
390 if let Some(neuron_idx) = ctx.neuron_index {
391 fields.insert(
392 "neuron_index".to_string(),
393 serde_json::Value::Number(serde_json::Number::from(neuron_idx)),
394 );
395 }
396 if let Some(epoch) = ctx.epoch {
397 fields.insert(
398 "epoch".to_string(),
399 serde_json::Value::Number(serde_json::Number::from(epoch)),
400 );
401 }
402 }
403
404 #[cfg(feature = "logging")]
405 {
406 log::log!(self.log_level, "{}", serde_json::Value::Object(fields));
407 }
408 }
409
410 #[cfg(not(feature = "serde"))]
411 {
412 let _ = error;
414 let _ = context;
415 }
416
417 #[cfg(all(feature = "logging", not(feature = "serde")))]
418 log::log!(self.log_level, "Error: {}", error);
419 }
420
421 fn log_simple_error(&self, error: &RuvFannError, context: Option<&ErrorContext>) {
422 let context_str = context
423 .map(|c| format!(" [{}]", c.operation))
424 .unwrap_or_default();
425
426 #[cfg(feature = "logging")]
427 log::log!(self.log_level, "Error{context_str}: {error}");
428 }
429}
430
431impl Default for ErrorLogger {
432 fn default() -> Self {
433 Self::new()
434 }
435}
436
437impl From<NetworkError> for RuvFannError {
439 fn from(error: NetworkError) -> Self {
440 match error {
441 NetworkError::InputSizeMismatch { expected, actual } => RuvFannError::Network {
442 category: NetworkErrorCategory::Topology,
443 message: format!("Input size mismatch: expected {expected}, got {actual}"),
444 context: None,
445 },
446 NetworkError::WeightCountMismatch { expected, actual } => RuvFannError::Network {
447 category: NetworkErrorCategory::Weights,
448 message: format!("Weight count mismatch: expected {expected}, got {actual}"),
449 context: None,
450 },
451 NetworkError::InvalidLayerConfiguration => RuvFannError::Network {
452 category: NetworkErrorCategory::Layers,
453 message: "Invalid layer configuration".to_string(),
454 context: None,
455 },
456 NetworkError::NoLayers => RuvFannError::Network {
457 category: NetworkErrorCategory::Topology,
458 message: "Network has no layers".to_string(),
459 context: None,
460 },
461 }
462 }
463}
464
465impl From<TrainingError> for RuvFannError {
466 fn from(error: TrainingError) -> Self {
467 match error {
468 TrainingError::InvalidData(msg) => RuvFannError::Validation {
469 category: ValidationErrorCategory::InputData,
470 message: msg,
471 details: vec![],
472 },
473 TrainingError::NetworkError(msg) => RuvFannError::Network {
474 category: NetworkErrorCategory::Topology,
475 message: msg,
476 context: None,
477 },
478 TrainingError::TrainingFailed(msg) => RuvFannError::Training {
479 category: TrainingErrorCategory::Algorithm,
480 message: msg,
481 context: None,
482 },
483 }
484 }
485}
486
487#[macro_export]
489macro_rules! network_error {
490 ($category:expr, $msg:expr) => {
491 RuvFannError::Network {
492 category: $category,
493 message: $msg.to_string(),
494 context: None,
495 }
496 };
497 ($category:expr, $msg:expr, $context:expr) => {
498 RuvFannError::Network {
499 category: $category,
500 message: $msg.to_string(),
501 context: Some($context.to_string()),
502 }
503 };
504}
505
506#[macro_export]
507macro_rules! training_error {
508 ($category:expr, $msg:expr) => {
509 RuvFannError::Training {
510 category: $category,
511 message: $msg.to_string(),
512 context: None,
513 }
514 };
515 ($category:expr, $msg:expr, $context:expr) => {
516 RuvFannError::Training {
517 category: $category,
518 message: $msg.to_string(),
519 context: Some($context.to_string()),
520 }
521 };
522}
523
524#[macro_export]
525macro_rules! cascade_error {
526 ($category:expr, $msg:expr) => {
527 RuvFannError::Cascade {
528 category: $category,
529 message: $msg.to_string(),
530 context: None,
531 }
532 };
533 ($category:expr, $msg:expr, $context:expr) => {
534 RuvFannError::Cascade {
535 category: $category,
536 message: $msg.to_string(),
537 context: Some($context.to_string()),
538 }
539 };
540}
541
542pub type RuvFannResult<T> = Result<T, RuvFannError>;
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548
549 #[test]
550 fn test_error_creation() {
551 let error = RuvFannError::Network {
552 category: NetworkErrorCategory::Topology,
553 message: "Test error".to_string(),
554 context: None,
555 };
556
557 assert!(matches!(error, RuvFannError::Network { .. }));
558 }
559
560 #[test]
561 fn test_error_context() {
562 let context = ErrorContext::new("test_operation")
563 .with_network_id("network_1")
564 .with_layer(2)
565 .with_epoch(100);
566
567 assert_eq!(context.operation, "test_operation");
568 assert_eq!(context.network_id, Some("network_1".to_string()));
569 assert_eq!(context.layer_index, Some(2));
570 assert_eq!(context.epoch, Some(100));
571 }
572
573 #[test]
574 fn test_recovery_context() {
575 let mut recovery = RecoveryContext::new(RecoveryStrategy::Retry);
576 assert!(recovery.should_retry());
577
578 recovery.max_retries = 2;
579 recovery.current_retry = 2;
580 assert!(!recovery.should_retry());
581 }
582
583 #[test]
584 fn test_error_conversion() {
585 let network_error = NetworkError::NoLayers;
586 let ruv_error: RuvFannError = network_error.into();
587
588 match ruv_error {
589 RuvFannError::Network { category, .. } => {
590 assert_eq!(category, NetworkErrorCategory::Topology);
591 }
592 _ => panic!("Expected Network error"),
593 }
594 }
595}