neuro_divergent_core/
error.rs

1//! Comprehensive error handling for the neuro-divergent library.
2//!
3//! This module provides detailed error types that cover all aspects of neural forecasting
4//! operations, from data validation to model training and prediction errors.
5
6use std::fmt;
7use thiserror::Error;
8
9/// Result type alias for neuro-divergent operations
10pub type NeuroDivergentResult<T> = Result<T, NeuroDivergentError>;
11
12/// Comprehensive error types for neuro-divergent operations
13#[derive(Error, Debug)]
14pub enum NeuroDivergentError {
15    /// Configuration errors in model setup
16    #[error("Model configuration error: {message}")]
17    ConfigError {
18        /// Error message describing the configuration issue
19        message: String,
20        /// Optional source error
21        source: Option<Box<dyn std::error::Error + Send + Sync>>,
22    },
23
24    /// Data validation and processing errors
25    #[error("Data validation error: {message}")]
26    DataError {
27        /// Error message describing the data issue
28        message: String,
29        /// Optional field name where the error occurred
30        field: Option<String>,
31        /// Optional source error
32        source: Option<Box<dyn std::error::Error + Send + Sync>>,
33    },
34
35    /// Training-related errors
36    #[error("Training error: {message}")]
37    TrainingError {
38        /// Error message describing the training issue
39        message: String,
40        /// Training epoch where the error occurred (if applicable)
41        epoch: Option<usize>,
42        /// Optional source error
43        source: Option<Box<dyn std::error::Error + Send + Sync>>,
44    },
45
46    /// Prediction and inference errors
47    #[error("Prediction error: {message}")]
48    PredictionError {
49        /// Error message describing the prediction issue
50        message: String,
51        /// Optional model name where the error occurred
52        model_name: Option<String>,
53        /// Optional source error
54        source: Option<Box<dyn std::error::Error + Send + Sync>>,
55    },
56
57    /// Integration errors with ruv-FANN
58    #[error("Network integration error: {0}")]
59    NetworkError(#[from] NetworkIntegrationError),
60
61    /// I/O errors (file operations, network, etc.)
62    #[error("I/O error: {0}")]
63    IoError(#[from] std::io::Error),
64
65    /// Serialization and deserialization errors
66    #[error("Serialization error: {message}")]
67    SerializationError {
68        /// Error message describing the serialization issue
69        message: String,
70        /// Optional format information
71        format: Option<String>,
72        /// Optional source error
73        source: Option<Box<dyn std::error::Error + Send + Sync>>,
74    },
75
76    /// Memory allocation and management errors
77    #[error("Memory error: {message}")]
78    MemoryError {
79        /// Error message describing the memory issue
80        message: String,
81        /// Optional memory usage information
82        memory_usage: Option<usize>,
83        /// Optional source error
84        source: Option<Box<dyn std::error::Error + Send + Sync>>,
85    },
86
87    /// Compatibility errors between components
88    #[error("Compatibility error: {message}")]
89    CompatibilityError {
90        /// Error message describing the compatibility issue
91        message: String,
92        /// Optional component names involved
93        components: Option<Vec<String>>,
94        /// Optional source error
95        source: Option<Box<dyn std::error::Error + Send + Sync>>,
96    },
97
98    /// Mathematical computation errors
99    #[error("Mathematical error: {message}")]
100    MathError {
101        /// Error message describing the mathematical issue
102        message: String,
103        /// Optional operation name
104        operation: Option<String>,
105        /// Optional source error
106        source: Option<Box<dyn std::error::Error + Send + Sync>>,
107    },
108
109    /// Parallel processing errors
110    #[error("Parallel processing error: {message}")]
111    ParallelError {
112        /// Error message describing the parallel processing issue
113        message: String,
114        /// Optional thread information
115        thread_info: Option<String>,
116        /// Optional source error
117        source: Option<Box<dyn std::error::Error + Send + Sync>>,
118    },
119
120    /// Time series specific errors
121    #[error("Time series error: {message}")]
122    TimeSeriesError {
123        /// Error message describing the time series issue
124        message: String,
125        /// Optional series identifier
126        series_id: Option<String>,
127        /// Optional timestamp information
128        timestamp: Option<chrono::DateTime<chrono::Utc>>,
129        /// Optional source error
130        source: Option<Box<dyn std::error::Error + Send + Sync>>,
131    },
132}
133
134/// Specific errors related to network integration with ruv-FANN
135#[derive(Error, Debug)]
136pub enum NetworkIntegrationError {
137    /// Network architecture mismatch
138    #[error("Network architecture mismatch: expected {expected}, found {found}")]
139    ArchitectureMismatch {
140        /// Expected network architecture
141        expected: String,
142        /// Found network architecture
143        found: String,
144    },
145
146    /// Network training algorithm error
147    #[error("Training algorithm error: {message}")]
148    TrainingAlgorithmError {
149        /// Error message
150        message: String,
151        /// Algorithm name
152        algorithm: Option<String>,
153    },
154
155    /// Network I/O error
156    #[error("Network I/O error: {message}")]
157    NetworkIoError {
158        /// Error message
159        message: String,
160        /// File path if applicable
161        path: Option<String>,
162    },
163
164    /// Network validation error
165    #[error("Network validation error: {message}")]
166    ValidationError {
167        /// Error message
168        message: String,
169        /// Layer information if applicable
170        layer: Option<usize>,
171    },
172
173    /// Network activation function error
174    #[error("Activation function error: {message}")]
175    ActivationError {
176        /// Error message
177        message: String,
178        /// Function name
179        function: Option<String>,
180    },
181}
182
183/// Error builder for creating detailed error instances
184pub struct ErrorBuilder {
185    error_type: ErrorType,
186    message: String,
187    source: Option<Box<dyn std::error::Error + Send + Sync>>,
188    context: std::collections::HashMap<String, String>,
189}
190
191/// Internal error type enumeration for the builder
192enum ErrorType {
193    Config,
194    Data,
195    Training,
196    Prediction,
197    Memory,
198    Compatibility,
199    Math,
200    Parallel,
201    TimeSeries,
202    Serialization,
203}
204
205impl ErrorBuilder {
206    /// Create a new configuration error builder
207    pub fn config<S: Into<String>>(message: S) -> Self {
208        Self {
209            error_type: ErrorType::Config,
210            message: message.into(),
211            source: None,
212            context: std::collections::HashMap::new(),
213        }
214    }
215
216    /// Create a new data error builder
217    pub fn data<S: Into<String>>(message: S) -> Self {
218        Self {
219            error_type: ErrorType::Data,
220            message: message.into(),
221            source: None,
222            context: std::collections::HashMap::new(),
223        }
224    }
225
226    /// Create a new training error builder
227    pub fn training<S: Into<String>>(message: S) -> Self {
228        Self {
229            error_type: ErrorType::Training,
230            message: message.into(),
231            source: None,
232            context: std::collections::HashMap::new(),
233        }
234    }
235
236    /// Create a new prediction error builder
237    pub fn prediction<S: Into<String>>(message: S) -> Self {
238        Self {
239            error_type: ErrorType::Prediction,
240            message: message.into(),
241            source: None,
242            context: std::collections::HashMap::new(),
243        }
244    }
245
246    /// Create a new memory error builder
247    pub fn memory<S: Into<String>>(message: S) -> Self {
248        Self {
249            error_type: ErrorType::Memory,
250            message: message.into(),
251            source: None,
252            context: std::collections::HashMap::new(),
253        }
254    }
255
256    /// Create a new time series error builder
257    pub fn time_series<S: Into<String>>(message: S) -> Self {
258        Self {
259            error_type: ErrorType::TimeSeries,
260            message: message.into(),
261            source: None,
262            context: std::collections::HashMap::new(),
263        }
264    }
265
266    /// Add a source error
267    pub fn source<E: std::error::Error + Send + Sync + 'static>(mut self, source: E) -> Self {
268        self.source = Some(Box::new(source));
269        self
270    }
271
272    /// Add context information
273    pub fn context<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
274        self.context.insert(key.into(), value.into());
275        self
276    }
277
278    /// Build the error
279    pub fn build(self) -> NeuroDivergentError {
280        match self.error_type {
281            ErrorType::Config => NeuroDivergentError::ConfigError {
282                message: self.message,
283                source: self.source,
284            },
285            ErrorType::Data => NeuroDivergentError::DataError {
286                message: self.message,
287                field: self.context.get("field").cloned(),
288                source: self.source,
289            },
290            ErrorType::Training => NeuroDivergentError::TrainingError {
291                message: self.message,
292                epoch: self.context.get("epoch").and_then(|s| s.parse().ok()),
293                source: self.source,
294            },
295            ErrorType::Prediction => NeuroDivergentError::PredictionError {
296                message: self.message,
297                model_name: self.context.get("model_name").cloned(),
298                source: self.source,
299            },
300            ErrorType::Memory => NeuroDivergentError::MemoryError {
301                message: self.message,
302                memory_usage: self.context.get("memory_usage").and_then(|s| s.parse().ok()),
303                source: self.source,
304            },
305            ErrorType::Compatibility => NeuroDivergentError::CompatibilityError {
306                message: self.message,
307                components: self.context.get("components")
308                    .map(|s| s.split(',').map(|s| s.trim().to_string()).collect()),
309                source: self.source,
310            },
311            ErrorType::Math => NeuroDivergentError::MathError {
312                message: self.message,
313                operation: self.context.get("operation").cloned(),
314                source: self.source,
315            },
316            ErrorType::Parallel => NeuroDivergentError::ParallelError {
317                message: self.message,
318                thread_info: self.context.get("thread_info").cloned(),
319                source: self.source,
320            },
321            ErrorType::TimeSeries => NeuroDivergentError::TimeSeriesError {
322                message: self.message,
323                series_id: self.context.get("series_id").cloned(),
324                timestamp: self.context.get("timestamp")
325                    .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
326                    .map(|dt| dt.with_timezone(&chrono::Utc)),
327                source: self.source,
328            },
329            ErrorType::Serialization => NeuroDivergentError::SerializationError {
330                message: self.message,
331                format: self.context.get("format").cloned(),
332                source: self.source,
333            },
334        }
335    }
336}
337
338/// Convenience macros for error creation
339#[macro_export]
340macro_rules! config_error {
341    ($msg:expr) => {
342        $crate::error::ErrorBuilder::config($msg).build()
343    };
344    ($msg:expr, $($key:expr => $value:expr),+) => {
345        {
346            let mut builder = $crate::error::ErrorBuilder::config($msg);
347            $(
348                builder = builder.context($key, $value);
349            )+
350            builder.build()
351        }
352    };
353}
354
355/// Create a data error with the given message
356#[macro_export]
357macro_rules! data_error {
358    ($msg:expr) => {
359        $crate::error::ErrorBuilder::data($msg).build()
360    };
361    ($msg:expr, field = $field:expr) => {
362        $crate::error::ErrorBuilder::data($msg).context("field", $field).build()
363    };
364    ($msg:expr, $($key:expr => $value:expr),+) => {
365        {
366            let mut builder = $crate::error::ErrorBuilder::data($msg);
367            $(
368                builder = builder.context($key, $value);
369            )+
370            builder.build()
371        }
372    };
373}
374
375/// Create a training error with the given message
376#[macro_export]
377macro_rules! training_error {
378    ($msg:expr) => {
379        $crate::error::ErrorBuilder::training($msg).build()
380    };
381    ($msg:expr, epoch = $epoch:expr) => {
382        $crate::error::ErrorBuilder::training($msg).context("epoch", $epoch.to_string()).build()
383    };
384    ($msg:expr, $($key:expr => $value:expr),+) => {
385        {
386            let mut builder = $crate::error::ErrorBuilder::training($msg);
387            $(
388                builder = builder.context($key, $value);
389            )+
390            builder.build()
391        }
392    };
393}
394
395#[cfg(test)]
396mod tests {
397    use super::*;
398
399    #[test]
400    fn test_error_builder_config() {
401        let error = ErrorBuilder::config("Test configuration error")
402            .context("parameter", "learning_rate")
403            .build();
404
405        match error {
406            NeuroDivergentError::ConfigError { message, .. } => {
407                assert_eq!(message, "Test configuration error");
408            }
409            _ => panic!("Expected ConfigError"),
410        }
411    }
412
413    #[test]
414    fn test_error_builder_data() {
415        let error = ErrorBuilder::data("Test data error")
416            .context("field", "target_column")
417            .build();
418
419        match error {
420            NeuroDivergentError::DataError { message, field, .. } => {
421                assert_eq!(message, "Test data error");
422                assert_eq!(field, Some("target_column".to_string()));
423            }
424            _ => panic!("Expected DataError"),
425        }
426    }
427
428    #[test]
429    fn test_error_macros() {
430        let error = config_error!("Configuration problem");
431        assert!(matches!(error, NeuroDivergentError::ConfigError { .. }));
432
433        let error = data_error!("Data problem", field = "timestamp");
434        match error {
435            NeuroDivergentError::DataError { field, .. } => {
436                assert_eq!(field, Some("timestamp".to_string()));
437            }
438            _ => panic!("Expected DataError"),
439        }
440    }
441
442    #[test]
443    fn test_network_integration_error() {
444        let error = NetworkIntegrationError::ArchitectureMismatch {
445            expected: "3-5-1".to_string(),
446            found: "3-4-1".to_string(),
447        };
448        
449        let error_string = error.to_string();
450        assert!(error_string.contains("3-5-1"));
451        assert!(error_string.contains("3-4-1"));
452    }
453
454    #[test]
455    fn test_error_chaining() {
456        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
457        let error = ErrorBuilder::data("Could not read data file")
458            .source(io_error)
459            .build();
460
461        match error {
462            NeuroDivergentError::DataError { source, .. } => {
463                assert!(source.is_some());
464            }
465            _ => panic!("Expected DataError"),
466        }
467    }
468}