ruv_fann/
errors.rs

1//! Comprehensive error handling system for ruv-FANN
2//!
3//! This module provides a unified error handling framework with detailed error categories,
4//! context information, and recovery mechanisms for robust neural network operations.
5
6use std::fmt;
7use std::error::Error;
8use thiserror::Error;
9use crate::{NetworkError, TrainingError};
10
11/// Main error type for all ruv-FANN operations
12#[derive(Error, Debug)]
13pub enum RuvFannError {
14    /// Network configuration and topology errors
15    #[error("Network error: {category:?} - {message}")]
16    Network {
17        category: NetworkErrorCategory,
18        message: String,
19        context: Option<String>,
20    },
21
22    /// Training and learning algorithm errors
23    #[error("Training error: {category:?} - {message}")]
24    Training {
25        category: TrainingErrorCategory,
26        message: String,
27        context: Option<String>,
28    },
29
30    /// Cascade correlation specific errors
31    #[error("Cascade error: {category:?} - {message}")]
32    Cascade {
33        category: CascadeErrorCategory,
34        message: String,
35        context: Option<String>,
36    },
37
38    /// Data validation and format errors
39    #[error("Validation error: {category:?} - {message}")]
40    Validation {
41        category: ValidationErrorCategory,
42        message: String,
43        details: Vec<String>,
44    },
45
46    /// I/O and serialization errors
47    #[error("I/O error: {category:?} - {message}")]
48    Io {
49        category: IoErrorCategory,
50        message: String,
51        source: Option<Box<dyn Error + Send + Sync>>,
52    },
53
54    /// Parallel processing and concurrency errors
55    #[error("Parallel processing error: {message}")]
56    Parallel {
57        message: String,
58        thread_count: usize,
59        context: Option<String>,
60    },
61
62    /// Memory allocation and management errors
63    #[error("Memory error: {message}")]
64    Memory {
65        message: String,
66        requested_bytes: Option<usize>,
67        available_bytes: Option<usize>,
68    },
69
70    /// Performance and optimization errors
71    #[error("Performance error: {message}")]
72    Performance {
73        message: String,
74        metric: String,
75        threshold: f64,
76        actual: f64,
77    },
78
79    /// FANN compatibility errors
80    #[error("FANN compatibility error: {message}")]
81    Compatibility {
82        message: String,
83        fann_version: Option<String>,
84        operation: String,
85    },
86}
87
88/// Network error categories for detailed classification
89#[derive(Debug, Clone, PartialEq)]
90pub enum NetworkErrorCategory {
91    /// Invalid network topology or structure
92    Topology,
93    /// Weight and bias configuration issues
94    Weights,
95    /// Layer configuration problems
96    Layers,
97    /// Neuron connection issues
98    Connections,
99    /// Activation function problems
100    Activation,
101    /// Forward propagation errors
102    Propagation,
103}
104
105/// Training error categories
106#[derive(Debug, Clone, PartialEq)]
107pub enum TrainingErrorCategory {
108    /// Learning algorithm failures
109    Algorithm,
110    /// Convergence problems
111    Convergence,
112    /// Gradient calculation issues
113    Gradients,
114    /// Learning rate problems
115    LearningRate,
116    /// Epoch and iteration errors
117    Iteration,
118    /// Stop criteria issues
119    StopCriteria,
120}
121
122/// Cascade correlation error categories
123#[derive(Debug, Clone, PartialEq)]
124pub enum CascadeErrorCategory {
125    /// Candidate neuron generation issues
126    CandidateGeneration,
127    /// Candidate training failures
128    CandidateTraining,
129    /// Candidate selection problems
130    CandidateSelection,
131    /// Network topology modification errors
132    TopologyModification,
133    /// Correlation calculation issues
134    CorrelationCalculation,
135    /// Output training problems
136    OutputTraining,
137}
138
139/// Validation error categories
140#[derive(Debug, Clone, PartialEq)]
141pub enum ValidationErrorCategory {
142    /// Input data validation
143    InputData,
144    /// Output data validation
145    OutputData,
146    /// Network configuration validation
147    NetworkConfig,
148    /// Training parameter validation
149    TrainingParams,
150    /// Cascade parameter validation
151    CascadeParams,
152}
153
154/// I/O error categories
155#[derive(Debug, Clone, PartialEq)]
156pub enum IoErrorCategory {
157    /// File reading/writing issues
158    FileAccess,
159    /// Serialization/deserialization problems
160    Serialization,
161    /// Format compatibility issues
162    Format,
163    /// Network export/import errors
164    NetworkIo,
165    /// Training data I/O problems
166    DataIo,
167}
168
169/// Comprehensive error category enum for uniform handling
170#[derive(Debug, Clone, PartialEq)]
171pub enum ErrorCategory {
172    Network(NetworkErrorCategory),
173    Training(TrainingErrorCategory),
174    Cascade(CascadeErrorCategory),
175    Validation(ValidationErrorCategory),
176    Io(IoErrorCategory),
177    Parallel,
178    Memory,
179    Performance,
180    Compatibility,
181}
182
183/// Validation error for detailed parameter checking
184#[derive(Error, Debug)]
185pub enum ValidationError {
186    #[error("Parameter out of range: {parameter} = {value}, expected {min} <= value <= {max}")]
187    OutOfRange {
188        parameter: String,
189        value: f64,
190        min: f64,
191        max: f64,
192    },
193
194    #[error("Invalid configuration: {message}")]
195    InvalidConfig { message: String },
196
197    #[error("Missing required parameter: {parameter}")]
198    MissingParameter { parameter: String },
199
200    #[error("Incompatible parameters: {message}")]
201    IncompatibleParams { message: String },
202
203    #[error("Data format error: {message}")]
204    DataFormat { message: String },
205}
206
207/// Error context for providing additional debugging information
208#[derive(Debug, Clone)]
209pub struct ErrorContext {
210    pub operation: String,
211    pub network_id: Option<String>,
212    pub layer_index: Option<usize>,
213    pub neuron_index: Option<usize>,
214    pub epoch: Option<usize>,
215    pub timestamp: std::time::SystemTime,
216    pub additional_info: std::collections::HashMap<String, String>,
217}
218
219impl ErrorContext {
220    pub fn new(operation: impl Into<String>) -> Self {
221        Self {
222            operation: operation.into(),
223            network_id: None,
224            layer_index: None,
225            neuron_index: None,
226            epoch: None,
227            timestamp: std::time::SystemTime::now(),
228            additional_info: std::collections::HashMap::new(),
229        }
230    }
231
232    pub fn with_network_id(mut self, id: impl Into<String>) -> Self {
233        self.network_id = Some(id.into());
234        self
235    }
236
237    pub fn with_layer(mut self, index: usize) -> Self {
238        self.layer_index = Some(index);
239        self
240    }
241
242    pub fn with_neuron(mut self, index: usize) -> Self {
243        self.neuron_index = Some(index);
244        self
245    }
246
247    pub fn with_epoch(mut self, epoch: usize) -> Self {
248        self.epoch = Some(epoch);
249        self
250    }
251
252    pub fn with_info(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
253        self.additional_info.insert(key.into(), value.into());
254        self
255    }
256}
257
258/// Error recovery strategies
259#[derive(Debug, Clone)]
260pub enum RecoveryStrategy {
261    /// Retry the operation with the same parameters
262    Retry,
263    /// Retry with modified parameters
264    RetryWithModification(std::collections::HashMap<String, String>),
265    /// Reset to a known good state
266    Reset,
267    /// Skip the problematic operation
268    Skip,
269    /// Abort the entire process
270    Abort,
271    /// Use fallback implementation
272    Fallback(String),
273}
274
275/// Error recovery context
276#[derive(Debug)]
277pub struct RecoveryContext {
278    pub strategy: RecoveryStrategy,
279    pub max_retries: usize,
280    pub current_retry: usize,
281    pub fallback_available: bool,
282    pub checkpoints: Vec<String>,
283}
284
285impl RecoveryContext {
286    pub fn new(strategy: RecoveryStrategy) -> Self {
287        Self {
288            strategy,
289            max_retries: 3,
290            current_retry: 0,
291            fallback_available: false,
292            checkpoints: Vec::new(),
293        }
294    }
295
296    pub fn should_retry(&self) -> bool {
297        self.current_retry < self.max_retries
298    }
299
300    pub fn increment_retry(&mut self) {
301        self.current_retry += 1;
302    }
303
304    pub fn reset_retry_count(&mut self) {
305        self.current_retry = 0;
306    }
307}
308
309/// Professional error logging and debugging facilities
310pub struct ErrorLogger {
311    log_level: log::Level,
312    structured_logging: bool,
313    performance_tracking: bool,
314}
315
316impl ErrorLogger {
317    pub fn new() -> Self {
318        Self {
319            log_level: log::Level::Warn,
320            structured_logging: true,
321            performance_tracking: false,
322        }
323    }
324
325    pub fn with_level(mut self, level: log::Level) -> Self {
326        self.log_level = level;
327        self
328    }
329
330    pub fn with_structured_logging(mut self, enabled: bool) -> Self {
331        self.structured_logging = enabled;
332        self
333    }
334
335    pub fn with_performance_tracking(mut self, enabled: bool) -> Self {
336        self.performance_tracking = enabled;
337        self
338    }
339
340    pub fn log_error(&self, error: &RuvFannError, context: Option<&ErrorContext>) {
341        if self.structured_logging {
342            self.log_structured_error(error, context);
343        } else {
344            self.log_simple_error(error, context);
345        }
346    }
347
348    fn log_structured_error(&self, error: &RuvFannError, context: Option<&ErrorContext>) {
349        let mut fields = serde_json::Map::new();
350        fields.insert("error_type".to_string(), serde_json::Value::String(format!("{:?}", error)));
351        fields.insert("message".to_string(), serde_json::Value::String(error.to_string()));
352
353        if let Some(ctx) = context {
354            fields.insert("operation".to_string(), serde_json::Value::String(ctx.operation.clone()));
355            if let Some(ref network_id) = ctx.network_id {
356                fields.insert("network_id".to_string(), serde_json::Value::String(network_id.clone()));
357            }
358            if let Some(layer_idx) = ctx.layer_index {
359                fields.insert("layer_index".to_string(), serde_json::Value::Number(serde_json::Number::from(layer_idx)));
360            }
361            if let Some(neuron_idx) = ctx.neuron_index {
362                fields.insert("neuron_index".to_string(), serde_json::Value::Number(serde_json::Number::from(neuron_idx)));
363            }
364            if let Some(epoch) = ctx.epoch {
365                fields.insert("epoch".to_string(), serde_json::Value::Number(serde_json::Number::from(epoch)));
366            }
367        }
368
369        #[cfg(feature = "logging")]
370        log::log!(self.log_level, "{}", serde_json::Value::Object(fields));
371    }
372
373    fn log_simple_error(&self, error: &RuvFannError, context: Option<&ErrorContext>) {
374        let context_str = context
375            .map(|c| format!(" [{}]", c.operation))
376            .unwrap_or_default();
377
378        #[cfg(feature = "logging")]
379        log::log!(self.log_level, "Error{}: {}", context_str, error);
380    }
381}
382
383impl Default for ErrorLogger {
384    fn default() -> Self {
385        Self::new()
386    }
387}
388
389/// Convert from legacy error types for backward compatibility
390impl From<NetworkError> for RuvFannError {
391    fn from(error: NetworkError) -> Self {
392        match error {
393            NetworkError::InputSizeMismatch { expected, actual } => {
394                RuvFannError::Network {
395                    category: NetworkErrorCategory::Topology,
396                    message: format!("Input size mismatch: expected {}, got {}", expected, actual),
397                    context: None,
398                }
399            }
400            NetworkError::WeightCountMismatch { expected, actual } => {
401                RuvFannError::Network {
402                    category: NetworkErrorCategory::Weights,
403                    message: format!("Weight count mismatch: expected {}, got {}", expected, actual),
404                    context: None,
405                }
406            }
407            NetworkError::InvalidLayerConfiguration => {
408                RuvFannError::Network {
409                    category: NetworkErrorCategory::Layers,
410                    message: "Invalid layer configuration".to_string(),
411                    context: None,
412                }
413            }
414            NetworkError::NoLayers => {
415                RuvFannError::Network {
416                    category: NetworkErrorCategory::Topology,
417                    message: "Network has no layers".to_string(),
418                    context: None,
419                }
420            }
421        }
422    }
423}
424
425impl From<TrainingError> for RuvFannError {
426    fn from(error: TrainingError) -> Self {
427        match error {
428            TrainingError::InvalidData(msg) => {
429                RuvFannError::Validation {
430                    category: ValidationErrorCategory::InputData,
431                    message: msg,
432                    details: vec![],
433                }
434            }
435            TrainingError::NetworkError(msg) => {
436                RuvFannError::Network {
437                    category: NetworkErrorCategory::Topology,
438                    message: msg,
439                    context: None,
440                }
441            }
442            TrainingError::TrainingFailed(msg) => {
443                RuvFannError::Training {
444                    category: TrainingErrorCategory::Algorithm,
445                    message: msg,
446                    context: None,
447                }
448            }
449        }
450    }
451}
452
453/// Helper macros for error creation with context
454#[macro_export]
455macro_rules! network_error {
456    ($category:expr, $msg:expr) => {
457        RuvFannError::Network {
458            category: $category,
459            message: $msg.to_string(),
460            context: None,
461        }
462    };
463    ($category:expr, $msg:expr, $context:expr) => {
464        RuvFannError::Network {
465            category: $category,
466            message: $msg.to_string(),
467            context: Some($context.to_string()),
468        }
469    };
470}
471
472#[macro_export]
473macro_rules! training_error {
474    ($category:expr, $msg:expr) => {
475        RuvFannError::Training {
476            category: $category,
477            message: $msg.to_string(),
478            context: None,
479        }
480    };
481    ($category:expr, $msg:expr, $context:expr) => {
482        RuvFannError::Training {
483            category: $category,
484            message: $msg.to_string(),
485            context: Some($context.to_string()),
486        }
487    };
488}
489
490#[macro_export]
491macro_rules! cascade_error {
492    ($category:expr, $msg:expr) => {
493        RuvFannError::Cascade {
494            category: $category,
495            message: $msg.to_string(),
496            context: None,
497        }
498    };
499    ($category:expr, $msg:expr, $context:expr) => {
500        RuvFannError::Cascade {
501            category: $category,
502            message: $msg.to_string(),
503            context: Some($context.to_string()),
504        }
505    };
506}
507
508/// Comprehensive result type for all ruv-FANN operations
509pub type RuvFannResult<T> = Result<T, RuvFannError>;
510
511#[cfg(test)]
512mod tests {
513    use super::*;
514
515    #[test]
516    fn test_error_creation() {
517        let error = RuvFannError::Network {
518            category: NetworkErrorCategory::Topology,
519            message: "Test error".to_string(),
520            context: None,
521        };
522
523        assert!(matches!(error, RuvFannError::Network { .. }));
524    }
525
526    #[test]
527    fn test_error_context() {
528        let context = ErrorContext::new("test_operation")
529            .with_network_id("network_1")
530            .with_layer(2)
531            .with_epoch(100);
532
533        assert_eq!(context.operation, "test_operation");
534        assert_eq!(context.network_id, Some("network_1".to_string()));
535        assert_eq!(context.layer_index, Some(2));
536        assert_eq!(context.epoch, Some(100));
537    }
538
539    #[test]
540    fn test_recovery_context() {
541        let mut recovery = RecoveryContext::new(RecoveryStrategy::Retry);
542        assert!(recovery.should_retry());
543
544        recovery.max_retries = 2;
545        recovery.current_retry = 2;
546        assert!(!recovery.should_retry());
547    }
548
549    #[test]
550    fn test_error_conversion() {
551        let network_error = NetworkError::NoLayers;
552        let ruv_error: RuvFannError = network_error.into();
553
554        match ruv_error {
555            RuvFannError::Network { category, .. } => {
556                assert_eq!(category, NetworkErrorCategory::Topology);
557            }
558            _ => panic!("Expected Network error"),
559        }
560    }
561}