1use std::fmt;
7use std::error::Error;
8use thiserror::Error;
9use crate::{NetworkError, TrainingError};
10
11#[derive(Error, Debug)]
13pub enum RuvFannError {
14 #[error("Network error: {category:?} - {message}")]
16 Network {
17 category: NetworkErrorCategory,
18 message: String,
19 context: Option<String>,
20 },
21
22 #[error("Training error: {category:?} - {message}")]
24 Training {
25 category: TrainingErrorCategory,
26 message: String,
27 context: Option<String>,
28 },
29
30 #[error("Cascade error: {category:?} - {message}")]
32 Cascade {
33 category: CascadeErrorCategory,
34 message: String,
35 context: Option<String>,
36 },
37
38 #[error("Validation error: {category:?} - {message}")]
40 Validation {
41 category: ValidationErrorCategory,
42 message: String,
43 details: Vec<String>,
44 },
45
46 #[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 #[error("Parallel processing error: {message}")]
56 Parallel {
57 message: String,
58 thread_count: usize,
59 context: Option<String>,
60 },
61
62 #[error("Memory error: {message}")]
64 Memory {
65 message: String,
66 requested_bytes: Option<usize>,
67 available_bytes: Option<usize>,
68 },
69
70 #[error("Performance error: {message}")]
72 Performance {
73 message: String,
74 metric: String,
75 threshold: f64,
76 actual: f64,
77 },
78
79 #[error("FANN compatibility error: {message}")]
81 Compatibility {
82 message: String,
83 fann_version: Option<String>,
84 operation: String,
85 },
86}
87
88#[derive(Debug, Clone, PartialEq)]
90pub enum NetworkErrorCategory {
91 Topology,
93 Weights,
95 Layers,
97 Connections,
99 Activation,
101 Propagation,
103}
104
105#[derive(Debug, Clone, PartialEq)]
107pub enum TrainingErrorCategory {
108 Algorithm,
110 Convergence,
112 Gradients,
114 LearningRate,
116 Iteration,
118 StopCriteria,
120}
121
122#[derive(Debug, Clone, PartialEq)]
124pub enum CascadeErrorCategory {
125 CandidateGeneration,
127 CandidateTraining,
129 CandidateSelection,
131 TopologyModification,
133 CorrelationCalculation,
135 OutputTraining,
137}
138
139#[derive(Debug, Clone, PartialEq)]
141pub enum ValidationErrorCategory {
142 InputData,
144 OutputData,
146 NetworkConfig,
148 TrainingParams,
150 CascadeParams,
152}
153
154#[derive(Debug, Clone, PartialEq)]
156pub enum IoErrorCategory {
157 FileAccess,
159 Serialization,
161 Format,
163 NetworkIo,
165 DataIo,
167}
168
169#[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#[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#[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#[derive(Debug, Clone)]
260pub enum RecoveryStrategy {
261 Retry,
263 RetryWithModification(std::collections::HashMap<String, String>),
265 Reset,
267 Skip,
269 Abort,
271 Fallback(String),
273}
274
275#[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
309pub 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
389impl 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#[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
508pub 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}