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 crate::{NetworkError, TrainingError};
7use std::error::Error;
8use thiserror::Error;
9
10/// Main error type for all ruv-FANN operations
11#[derive(Error, Debug)]
12pub enum RuvFannError {
13    /// Network configuration and topology errors
14    #[error("Network error: {category:?} - {message}")]
15    Network {
16        category: NetworkErrorCategory,
17        message: String,
18        context: Option<String>,
19    },
20
21    /// Training and learning algorithm errors
22    #[error("Training error: {category:?} - {message}")]
23    Training {
24        category: TrainingErrorCategory,
25        message: String,
26        context: Option<String>,
27    },
28
29    /// Cascade correlation specific errors
30    #[error("Cascade error: {category:?} - {message}")]
31    Cascade {
32        category: CascadeErrorCategory,
33        message: String,
34        context: Option<String>,
35    },
36
37    /// Data validation and format errors
38    #[error("Validation error: {category:?} - {message}")]
39    Validation {
40        category: ValidationErrorCategory,
41        message: String,
42        details: Vec<String>,
43    },
44
45    /// I/O and serialization errors
46    #[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    /// Parallel processing and concurrency errors
54    #[error("Parallel processing error: {message}")]
55    Parallel {
56        message: String,
57        thread_count: usize,
58        context: Option<String>,
59    },
60
61    /// Memory allocation and management errors
62    #[error("Memory error: {message}")]
63    Memory {
64        message: String,
65        requested_bytes: Option<usize>,
66        available_bytes: Option<usize>,
67    },
68
69    /// Performance and optimization errors
70    #[error("Performance error: {message}")]
71    Performance {
72        message: String,
73        metric: String,
74        threshold: f64,
75        actual: f64,
76    },
77
78    /// FANN compatibility errors
79    #[error("FANN compatibility error: {message}")]
80    Compatibility {
81        message: String,
82        fann_version: Option<String>,
83        operation: String,
84    },
85}
86
87/// Network error categories for detailed classification
88#[derive(Debug, Clone, PartialEq)]
89pub enum NetworkErrorCategory {
90    /// Invalid network topology or structure
91    Topology,
92    /// Weight and bias configuration issues
93    Weights,
94    /// Layer configuration problems
95    Layers,
96    /// Neuron connection issues
97    Connections,
98    /// Activation function problems
99    Activation,
100    /// Forward propagation errors
101    Propagation,
102}
103
104/// Training error categories
105#[derive(Debug, Clone, PartialEq)]
106pub enum TrainingErrorCategory {
107    /// Learning algorithm failures
108    Algorithm,
109    /// Convergence problems
110    Convergence,
111    /// Gradient calculation issues
112    Gradients,
113    /// Learning rate problems
114    LearningRate,
115    /// Epoch and iteration errors
116    Iteration,
117    /// Stop criteria issues
118    StopCriteria,
119}
120
121/// Cascade correlation error categories
122#[derive(Debug, Clone, PartialEq)]
123pub enum CascadeErrorCategory {
124    /// Candidate neuron generation issues
125    CandidateGeneration,
126    /// Candidate training failures
127    CandidateTraining,
128    /// Candidate selection problems
129    CandidateSelection,
130    /// Network topology modification errors
131    TopologyModification,
132    /// Correlation calculation issues
133    CorrelationCalculation,
134    /// Output training problems
135    OutputTraining,
136}
137
138/// Validation error categories
139#[derive(Debug, Clone, PartialEq)]
140pub enum ValidationErrorCategory {
141    /// Input data validation
142    InputData,
143    /// Output data validation
144    OutputData,
145    /// Network configuration validation
146    NetworkConfig,
147    /// Training parameter validation
148    TrainingParams,
149    /// Cascade parameter validation
150    CascadeParams,
151}
152
153/// I/O error categories
154#[derive(Debug, Clone, PartialEq)]
155pub enum IoErrorCategory {
156    /// File reading/writing issues
157    FileAccess,
158    /// Serialization/deserialization problems
159    Serialization,
160    /// Format compatibility issues
161    Format,
162    /// Network export/import errors
163    NetworkIo,
164    /// Training data I/O problems
165    DataIo,
166}
167
168/// Comprehensive error category enum for uniform handling
169#[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/// Validation error for detailed parameter checking
183#[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/// Error context for providing additional debugging information
207#[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/// Error recovery strategies
258#[derive(Debug, Clone)]
259pub enum RecoveryStrategy {
260    /// Retry the operation with the same parameters
261    Retry,
262    /// Retry with modified parameters
263    RetryWithModification(std::collections::HashMap<String, String>),
264    /// Reset to a known good state
265    Reset,
266    /// Skip the problematic operation
267    Skip,
268    /// Abort the entire process
269    Abort,
270    /// Use fallback implementation
271    Fallback(String),
272}
273
274/// Error recovery context
275#[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
308/// Professional error logging and debugging facilities
309pub struct ErrorLogger {
310    #[cfg(feature = "logging")]
311    log_level: log::Level,
312    #[cfg(not(feature = "logging"))]
313    log_level: u8, // Simple placeholder when log feature is disabled
314    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, // 2 as a placeholder for Warn level
325            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        // No-op when logging is disabled
339        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            // Simple fallback when serde_json is not available
413            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
437/// Convert from legacy error types for backward compatibility
438impl 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/// Helper macros for error creation with context
488#[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
542/// Comprehensive result type for all ruv-FANN operations
543pub 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}