Skip to main content

scirs2_core/error/
recovery.rs

1//! Advanced error recovery and resilience mechanisms for ``SciRS2``
2//!
3//! This module provides sophisticated error handling patterns including:
4//! - Automatic retry mechanisms with backoff strategies
5//! - Circuit breaker patterns for fault tolerance
6//! - Error recovery hints and suggestions
7//! - Graceful degradation strategies
8//! - Error aggregation and reporting
9
10use std::fmt;
11use std::sync::{Arc, Mutex};
12use std::time::{Duration, Instant};
13
14use crate::error::{CoreError, CoreResult, ErrorContext};
15
16/// Recovery strategy for handling errors
17#[derive(Debug, Clone)]
18pub enum RecoveryStrategy {
19    /// Fail immediately without retry
20    FailFast,
21    /// Retry with exponential backoff
22    ExponentialBackoff {
23        max_attempts: usize,
24        initialdelay: Duration,
25        maxdelay: Duration,
26        multiplier: f64,
27    },
28    /// Retry with linear backoff
29    LinearBackoff {
30        max_attempts: usize,
31        delay: Duration,
32    },
33    /// Retry with custom backoff strategy
34    CustomBackoff {
35        max_attempts: usize,
36        delays: Vec<Duration>,
37    },
38    /// Circuit breaker pattern
39    CircuitBreaker {
40        failure_threshold: usize,
41        timeout: Duration,
42        recoverytimeout: Duration,
43    },
44    /// Fallback to alternative implementation
45    Fallback,
46    /// Graceful degradation with reduced functionality
47    GracefulDegradation,
48}
49
50impl Default for RecoveryStrategy {
51    fn default() -> Self {
52        Self::ExponentialBackoff {
53            max_attempts: 3,
54            initialdelay: Duration::from_millis(100),
55            maxdelay: Duration::from_secs(5),
56            multiplier: 2.0,
57        }
58    }
59}
60
61/// Recovery hint providing actionable suggestions for error resolution
62#[derive(Debug, Clone)]
63pub struct RecoveryHint {
64    /// Short description of the suggested action
65    pub action: String,
66    /// Detailed explanation of why this action might help
67    pub explanation: String,
68    /// Code examples or specific steps to take
69    pub examples: Vec<String>,
70    /// Confidence level that this hint will resolve the issue (0.0 to 1.0)
71    pub confidence: f64,
72}
73
74impl RecoveryHint {
75    /// Create a new recovery hint
76    pub fn new<S: Into<String>>(action: S, explanation: S, confidence: f64) -> Self {
77        Self {
78            action: action.into(),
79            explanation: explanation.into(),
80            examples: Vec::new(),
81            confidence: confidence.clamp(0.0, 1.0),
82        }
83    }
84
85    /// Add an example to the recovery hint
86    pub fn with_example<S: Into<String>>(mut self, example: S) -> Self {
87        self.examples.push(example.into());
88        self
89    }
90
91    /// Add multiple examples to the recovery hint
92    pub fn with_examples<I, S>(mut self, examples: I) -> Self
93    where
94        I: IntoIterator<Item = S>,
95        S: Into<String>,
96    {
97        self.examples.extend(examples.into_iter().map(|s| s.into()));
98        self
99    }
100}
101
102impl fmt::Display for RecoveryHint {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        writeln!(
105            f,
106            "šŸ’” {} (confidence: {:.1}%)",
107            self.action,
108            self.confidence * 100.0
109        )?;
110        writeln!(f, "   {}", self.explanation)?;
111
112        if !self.examples.is_empty() {
113            writeln!(f, "   Examples:")?;
114            for (i, example) in self.examples.iter().enumerate() {
115                writeln!(f, "   {}. {}", i + 1, example)?;
116            }
117        }
118
119        Ok(())
120    }
121}
122
123/// Enhanced error with recovery capabilities
124#[derive(Debug, Clone)]
125pub struct RecoverableError {
126    /// The original error
127    pub error: CoreError,
128    /// Suggested recovery strategy
129    pub strategy: RecoveryStrategy,
130    /// Recovery hints for the user
131    pub hints: Vec<RecoveryHint>,
132    /// Whether this error is retryable
133    pub retryable: bool,
134    /// Error severity level
135    pub severity: ErrorSeverity,
136    /// Additional metadata about the error
137    pub metadata: std::collections::HashMap<String, String>,
138}
139
140/// Error severity levels
141#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
142pub enum ErrorSeverity {
143    /// Informational - operation succeeded with warnings
144    Info,
145    /// Warning - operation succeeded but with issues
146    Warning,
147    /// Error - operation failed but system is stable
148    Error,
149    /// Critical - operation failed and system stability affected
150    Critical,
151    /// Fatal - operation failed and system cannot continue
152    Fatal,
153}
154
155impl fmt::Display for ErrorSeverity {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        match self {
158            Self::Info => write!(f, "INFO"),
159            Self::Warning => write!(f, "WARN"),
160            Self::Error => write!(f, "ERROR"),
161            Self::Critical => write!(f, "CRITICAL"),
162            Self::Fatal => write!(f, "FATAL"),
163        }
164    }
165}
166
167impl RecoverableError {
168    /// Create a new recoverable error
169    pub fn error(error: CoreError) -> Self {
170        let (strategy, hints, retryable, severity) = Self::analyzeerror(&error);
171
172        Self {
173            error,
174            strategy,
175            hints,
176            retryable,
177            severity,
178            metadata: std::collections::HashMap::new(),
179        }
180    }
181
182    /// Create a recoverable error with custom strategy
183    pub fn with_strategy(mut self, strategy: RecoveryStrategy) -> Self {
184        self.strategy = strategy;
185        self
186    }
187
188    /// Add a recovery hint
189    pub fn with_hint(mut self, hint: RecoveryHint) -> Self {
190        self.hints.push(hint);
191        self
192    }
193
194    /// Add metadata to the error
195    pub fn with_metadata<K, V>(mut self, key: K, value: V) -> Self
196    where
197        K: Into<String>,
198        V: Into<String>,
199    {
200        self.metadata.insert(key.into(), value.into());
201        self
202    }
203
204    /// Set error severity
205    pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
206        self.severity = severity;
207        self
208    }
209
210    /// Analyze an error and suggest recovery strategies
211    fn analyzeerror(
212        error: &CoreError,
213    ) -> (RecoveryStrategy, Vec<RecoveryHint>, bool, ErrorSeverity) {
214        match error {
215            CoreError::DomainError(_) => (
216                RecoveryStrategy::FailFast,
217                vec![
218                    RecoveryHint::new(
219                        "Check input values",
220                        "Domain errors usually indicate input values are outside the valid range",
221                        0.9,
222                    ).with_examples(vec![
223                        "Ensure all inputs are finite (not NaN or infinity)",
224                        "Check that array indices are within bounds",
225                        "Verify parameter ranges match function requirements",
226                    ]),
227                ],
228                false,
229                ErrorSeverity::Error,
230            ),
231
232            CoreError::ConvergenceError(_) => (
233                RecoveryStrategy::ExponentialBackoff {
234                    max_attempts: 5,
235                    initialdelay: Duration::from_millis(500),
236                    maxdelay: Duration::from_secs(10),
237                    multiplier: 1.5,
238                },
239                vec![
240                    RecoveryHint::new(
241                        "Adjust convergence parameters",
242                        "Convergence failures often indicate numerical instability or poor initial conditions",
243                        0.8,
244                    ).with_examples(vec![
245                        "Increase maximum iteration count",
246                        "Decrease convergence tolerance",
247                        "Try different initial values or starting points",
248                        "Use a more robust algorithm variant",
249                    ]),
250                    RecoveryHint::new(
251                        "Check problem conditioning",
252                        "Poor problem conditioning can prevent convergence",
253                        0.7,
254                    ).with_examples(vec![
255                        "Scale input data to similar ranges",
256                        "Add regularization to improve conditioning",
257                        "Use preconditioning techniques",
258                    ]),
259                ],
260                true,
261                ErrorSeverity::Warning,
262            ),
263
264            CoreError::MemoryError(_) => (
265                RecoveryStrategy::GracefulDegradation,
266                vec![
267                    RecoveryHint::new(
268                        "Reduce memory usage",
269                        "Memory errors indicate insufficient resources for the requested operation",
270                        0.9,
271                    ).with_examples(vec![
272                        "Process data in smaller chunks",
273                        "Use out-of-core algorithms",
274                        "Reduce precision (e.g., f32 instead of f64)",
275                        "Free unused variables before large operations",
276                    ]),
277                    RecoveryHint::new(
278                        "Use streaming algorithms",
279                        "Stream processing can handle larger datasets with limited memory",
280                        0.8,
281                    ).with_examples(vec![
282                        "Enable chunked processing with scirs2_core::memory_efficient",
283                        "Use iterative algorithms instead of direct methods",
284                        "Consider using memory-mapped files for large arrays",
285                    ]),
286                ],
287                false,
288                ErrorSeverity::Critical,
289            ),
290
291            CoreError::TimeoutError(_) => (
292                RecoveryStrategy::LinearBackoff {
293                    max_attempts: 3,
294                    delay: Duration::from_secs(1),
295                },
296                vec![
297                    RecoveryHint::new(
298                        "Increase timeout duration",
299                        "Timeout errors may indicate the operation needs more time",
300                        0.7,
301                    ).with_examples(vec![
302                        "Set a larger timeout value",
303                        "Use asynchronous operations with progress tracking",
304                        "Break large operations into smaller parts",
305                    ]),
306                ],
307                true,
308                ErrorSeverity::Warning,
309            ),
310
311            CoreError::ShapeError(_) | CoreError::DimensionError(_) => (
312                RecoveryStrategy::FailFast,
313                vec![
314                    RecoveryHint::new(
315                        "Verify array dimensions",
316                        "Shape/dimension errors indicate incompatible array sizes",
317                        0.95,
318                    ).with_examples(vec![
319                        "Check input array shapes with .shape() method",
320                        "Ensure matrix dimensions are compatible for operations",
321                        "Use broadcasting or reshaping to make arrays compatible",
322                        "Transpose arrays if needed (e.g., .t() for transpose)",
323                    ]),
324                ],
325                false,
326                ErrorSeverity::Error,
327            ),
328
329            CoreError::NotImplementedError(_) => (
330                RecoveryStrategy::Fallback,
331                vec![
332                    RecoveryHint::new(
333                        "Use alternative implementation",
334                        "This feature is not yet implemented in `SciRS2`",
335                        0.8,
336                    ).with_examples(vec![
337                        "Check if a similar function exists in another module",
338                        "Use a more basic implementation as a workaround",
339                        "Consider using external libraries for this functionality",
340                    ]),
341                ],
342                false,
343                ErrorSeverity::Info,
344            ),
345
346            CoreError::IoError(_) => (
347                RecoveryStrategy::ExponentialBackoff {
348                    max_attempts: 3,
349                    initialdelay: Duration::from_millis(200),
350                    maxdelay: Duration::from_secs(2),
351                    multiplier: 2.0,
352                },
353                vec![
354                    RecoveryHint::new(
355                        "Check file permissions and paths",
356                        "I/O errors often indicate file system issues",
357                        0.8,
358                    ).with_examples(vec![
359                        "Verify file paths exist and are accessible",
360                        "Check read/write permissions",
361                        "Ensure sufficient disk space is available",
362                        "Try absolute paths instead of relative paths",
363                    ]),
364                ],
365                true,
366                ErrorSeverity::Warning,
367            ), _ => (
368                RecoveryStrategy::default(),
369                vec![
370                    RecoveryHint::new(
371                        "Check error details",
372                        "Review the specific error message for more information",
373                        0.5,
374                    ),
375                ],
376                true,
377                ErrorSeverity::Error,
378            ),
379        }
380    }
381
382    /// Get a user-friendly error report with recovery suggestions
383    pub fn recovery_report(&self) -> String {
384        let mut report = String::new();
385
386        report.push_str(&format!("🚨 {} Error: {}\n\n", self.severity, self.error));
387
388        if self.retryable {
389            report.push_str("āœ… This error may be retryable\n");
390        } else {
391            report.push_str("āŒ This error is not retryable\n");
392        }
393
394        report.push_str(&format!("šŸ”§ Suggested strategy: {:?}\n\n", self.strategy));
395
396        if !self.hints.is_empty() {
397            report.push_str("šŸ” Recovery suggestions:\n");
398            for (i, hint) in self.hints.iter().enumerate() {
399                report.push_str(&format!("{}. {}\n", i + 1, hint));
400            }
401        }
402
403        if !self.metadata.is_empty() {
404            report.push_str("\nšŸ“‹ Additional information:\n");
405            for (key, value) in &self.metadata {
406                report.push_str(&format!("   {key}: {value}\n"));
407            }
408        }
409
410        report
411    }
412}
413
414impl fmt::Display for RecoverableError {
415    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416        write!(f, "{}", self.recovery_report())
417    }
418}
419
420impl std::error::Error for RecoverableError {
421    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
422        Some(&self.error)
423    }
424}
425
426/// Circuit breaker for handling repeated failures
427#[derive(Debug)]
428pub struct CircuitBreaker {
429    failure_threshold: usize,
430    #[allow(dead_code)]
431    timeout: Duration,
432    recoverytimeout: Duration,
433    state: Arc<Mutex<CircuitBreakerState>>,
434}
435
436#[derive(Debug)]
437struct CircuitBreakerState {
438    failure_count: usize,
439    last_failure_time: Option<Instant>,
440    state: CircuitState,
441}
442
443#[derive(Debug, Clone, Copy, PartialEq, Eq)]
444pub enum CircuitState {
445    Closed,
446    Open,
447    HalfOpen,
448}
449
450impl CircuitBreaker {
451    /// Create a new circuit breaker
452    pub fn timeout(failure_threshold: usize, timeout: Duration, recoverytimeout: Duration) -> Self {
453        Self {
454            failure_threshold,
455            timeout,
456            recoverytimeout,
457            state: Arc::new(Mutex::new(CircuitBreakerState {
458                failure_count: 0,
459                last_failure_time: None,
460                state: CircuitState::Closed,
461            })),
462        }
463    }
464
465    /// Create a new circuit breaker (alias for timeout method)
466    pub fn new(failure_threshold: usize, timeout: Duration, recoverytimeout: Duration) -> Self {
467        Self::timeout(failure_threshold, timeout, recoverytimeout)
468    }
469
470    /// Execute a function with circuit breaker protection
471    pub fn execute<F, T>(&self, f: F) -> CoreResult<T>
472    where
473        F: FnOnce() -> CoreResult<T>,
474    {
475        // Check if circuit should allow execution
476        if !self.should_allow_execution() {
477            return Err(CoreError::ComputationError(ErrorContext::new(
478                "Circuit breaker is open - too many recent failures",
479            )));
480        }
481
482        // Execute the function
483        match f() {
484            Ok(result) => {
485                self.on_success();
486                Ok(result)
487            }
488            Err(err) => {
489                self.on_failure();
490                Err(err)
491            }
492        }
493    }
494
495    fn should_allow_execution(&self) -> bool {
496        let mut state = self.state.lock().expect("Operation failed");
497
498        match state.state {
499            CircuitState::Closed => true,
500            CircuitState::Open => {
501                if let Some(last_failure) = state.last_failure_time {
502                    if last_failure.elapsed() >= self.recoverytimeout {
503                        state.state = CircuitState::HalfOpen;
504                        true
505                    } else {
506                        false
507                    }
508                } else {
509                    true
510                }
511            }
512            CircuitState::HalfOpen => true,
513        }
514    }
515
516    fn on_success(&self) {
517        let mut state = self.state.lock().expect("Operation failed");
518        state.failure_count = 0;
519        state.state = CircuitState::Closed;
520    }
521
522    fn on_failure(&self) {
523        let mut state = self.state.lock().expect("Operation failed");
524        state.failure_count += 1;
525        state.last_failure_time = Some(Instant::now());
526
527        if state.failure_count >= self.failure_threshold {
528            state.state = CircuitState::Open;
529        }
530    }
531
532    /// Get current circuit breaker status
533    pub fn status(&self) -> CircuitBreakerStatus {
534        let state = self.state.lock().expect("Operation failed");
535        CircuitBreakerStatus {
536            state: state.state,
537            failure_count: state.failure_count,
538            failure_threshold: self.failure_threshold,
539        }
540    }
541}
542
543/// Circuit breaker status information
544#[derive(Debug, Clone)]
545pub struct CircuitBreakerStatus {
546    pub state: CircuitState,
547    pub failure_count: usize,
548    pub failure_threshold: usize,
549}
550
551impl fmt::Display for CircuitBreakerStatus {
552    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
553        write!(
554            f,
555            "Circuit breaker: {state:?} ({failure_count}/{failure_threshold} failures)",
556            state = self.state,
557            failure_count = self.failure_count,
558            failure_threshold = self.failure_threshold
559        )
560    }
561}
562
563/// Retry executor with configurable backoff strategies
564#[derive(Debug)]
565pub struct RetryExecutor {
566    strategy: RecoveryStrategy,
567}
568
569impl RetryExecutor {
570    /// Create a new retry executor with the given strategy
571    pub fn strategy(strategy: RecoveryStrategy) -> Self {
572        Self { strategy }
573    }
574
575    /// Create a new retry executor (alias for strategy method)
576    pub fn new(strategy: RecoveryStrategy) -> Self {
577        Self::strategy(strategy)
578    }
579
580    /// Execute a function with retry logic
581    pub fn execute<F, T>(&self, mut f: F) -> CoreResult<T>
582    where
583        F: FnMut() -> CoreResult<T>,
584    {
585        match &self.strategy {
586            RecoveryStrategy::FailFast => f(),
587
588            RecoveryStrategy::ExponentialBackoff {
589                max_attempts,
590                initialdelay,
591                maxdelay,
592                multiplier,
593            } => {
594                let mut delay = *initialdelay;
595                let mut lasterror = None;
596
597                for attempt in 0..*max_attempts {
598                    match f() {
599                        Ok(result) => return Ok(result),
600                        Err(err) => {
601                            lasterror = Some(err);
602
603                            if attempt < max_attempts - 1 {
604                                std::thread::sleep(delay);
605                                delay = std::cmp::min(
606                                    Duration::from_nanos(
607                                        (delay.as_nanos() as f64 * multiplier) as u64,
608                                    ),
609                                    *maxdelay,
610                                );
611                            }
612                        }
613                    }
614                }
615
616                Err(lasterror.expect("Operation failed"))
617            }
618
619            RecoveryStrategy::LinearBackoff {
620                max_attempts,
621                delay,
622            } => {
623                let mut lasterror = None;
624
625                for attempt in 0..*max_attempts {
626                    match f() {
627                        Ok(result) => return Ok(result),
628                        Err(err) => {
629                            lasterror = Some(err);
630
631                            if attempt < max_attempts - 1 {
632                                std::thread::sleep(*delay);
633                            }
634                        }
635                    }
636                }
637
638                Err(lasterror.expect("Operation failed"))
639            }
640
641            RecoveryStrategy::CustomBackoff {
642                max_attempts,
643                delays,
644            } => {
645                let mut lasterror = None;
646
647                for attempt in 0..*max_attempts {
648                    match f() {
649                        Ok(result) => return Ok(result),
650                        Err(err) => {
651                            lasterror = Some(err);
652
653                            if attempt < max_attempts - 1 {
654                                if let Some(&delay) = delays.get(attempt) {
655                                    std::thread::sleep(delay);
656                                }
657                            }
658                        }
659                    }
660                }
661
662                Err(lasterror.expect("Operation failed"))
663            }
664
665            _ => f(), // Other strategies not applicable for retry
666        }
667    }
668}
669
670/// Error aggregator for collecting multiple errors
671#[derive(Debug, Default)]
672pub struct ErrorAggregator {
673    errors: Vec<RecoverableError>,
674    maxerrors: Option<usize>,
675}
676
677impl ErrorAggregator {
678    /// Create a new error aggregator
679    pub fn new() -> Self {
680        Self::default()
681    }
682
683    /// Create a new error aggregator with maximum error limit
684    pub fn errors(maxerrors: usize) -> Self {
685        Self {
686            errors: Vec::new(),
687            maxerrors: Some(maxerrors),
688        }
689    }
690
691    /// Add an error to the aggregator
692    pub fn adderror(&mut self, error: RecoverableError) {
693        if let Some(max) = self.maxerrors {
694            if self.errors.len() >= max {
695                return; // Ignore additional errors
696            }
697        }
698
699        self.errors.push(error);
700    }
701
702    /// Add a simple error to the aggregator
703    pub fn add_simpleerror(&mut self, error: CoreError) {
704        self.adderror(RecoverableError::error(error));
705    }
706
707    /// Check if there are any errors
708    pub fn haserrors(&self) -> bool {
709        !self.errors.is_empty()
710    }
711
712    /// Get the number of errors
713    pub fn error_count(&self) -> usize {
714        self.errors.len()
715    }
716
717    /// Get all errors
718    pub fn errors_2(&self) -> &[RecoverableError] {
719        &self.errors
720    }
721
722    /// Get the most severe error
723    pub fn most_severeerror(&self) -> Option<&RecoverableError> {
724        self.errors.iter().max_by_key(|err| err.severity)
725    }
726
727    /// Get a summary of all errors
728    pub fn summary(&self) -> String {
729        if self.errors.is_empty() {
730            return "No errors".to_string();
731        }
732
733        let mut summary = format!("Collected {count} error(s):\n", count = self.errors.len());
734
735        for (i, error) in self.errors.iter().enumerate() {
736            summary.push_str(&format!(
737                "{num}. [{severity}] {error}\n",
738                num = i + 1,
739                severity = error.severity,
740                error = error.error
741            ));
742        }
743
744        if let Some(most_severe) = self.most_severeerror() {
745            summary.push_str(&format!(
746                "\nMost severe: {error}\n",
747                error = most_severe.error
748            ));
749        }
750
751        summary
752    }
753
754    /// Convert to a single error if there are any errors
755    pub fn into_result<T>(self, successvalue: T) -> Result<T, Box<RecoverableError>> {
756        if let Some(most_severe) = self.errors.into_iter().max_by_key(|err| err.severity) {
757            Err(Box::new(most_severe))
758        } else {
759            Ok(successvalue)
760        }
761    }
762
763    /// Clear all collected errors
764    pub fn clear(&mut self) {
765        self.errors.clear();
766    }
767}
768
769impl fmt::Display for ErrorAggregator {
770    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771        write!(f, "{}", self.summary())
772    }
773}
774
775/// Convenience function to create common recovery hints
776pub mod hints {
777    use super::RecoveryHint;
778
779    /// Create a hint for checking input values
780    pub fn check_inputs() -> RecoveryHint {
781        RecoveryHint::new(
782            "Verify input parameters",
783            "Invalid inputs are a common source of errors",
784            0.8,
785        )
786        .with_examples(vec![
787            "Check for NaN or infinite values",
788            "Ensure arrays have the correct shape",
789            "Verify parameter ranges",
790        ])
791    }
792
793    /// Create a hint for numerical stability issues
794    pub fn numerical_stability() -> RecoveryHint {
795        RecoveryHint::new(
796            "Improve numerical stability",
797            "Numerical instability can cause computation failures",
798            0.7,
799        )
800        .with_examples(vec![
801            "Scale input data to similar ranges",
802            "Use higher precision arithmetic",
803            "Add regularization or conditioning",
804        ])
805    }
806
807    /// Create a hint for memory optimization
808    pub fn memory_optimization() -> RecoveryHint {
809        RecoveryHint::new(
810            "Optimize memory usage",
811            "Large datasets may require memory-efficient approaches",
812            0.9,
813        )
814        .with_examples(vec![
815            "Process data in chunks",
816            "Use streaming algorithms",
817            "Reduce precision if appropriate",
818        ])
819    }
820
821    /// Create a hint for algorithm selection
822    pub fn algorithm_selection() -> RecoveryHint {
823        RecoveryHint::new(
824            "Try alternative algorithms",
825            "Different algorithms may work better for your specific problem",
826            0.6,
827        )
828        .with_examples(vec![
829            "Use iterative instead of direct methods",
830            "Try a more robust variant",
831            "Consider approximate methods",
832        ])
833    }
834}
835
836#[cfg(test)]
837mod tests {
838    use super::*;
839    use crate::error::ErrorContext;
840
841    #[test]
842    fn test_recovery_hint_creation() {
843        let hint = RecoveryHint::new("Test action", "Test explanation", 0.8)
844            .with_example("Example 1")
845            .with_example("Example 2");
846
847        assert_eq!(hint.action, "Test action");
848        assert_eq!(hint.confidence, 0.8);
849        assert_eq!(hint.examples.len(), 2);
850    }
851
852    #[test]
853    fn test_recoverableerror_analysis() {
854        let domainerror = CoreError::DomainError(ErrorContext::new("Test domain error"));
855        let recoverable = RecoverableError::error(domainerror);
856
857        assert!(!recoverable.retryable);
858        assert_eq!(recoverable.severity, ErrorSeverity::Error);
859        assert!(!recoverable.hints.is_empty());
860    }
861
862    #[test]
863    fn test_circuitbreaker() {
864        let cb = CircuitBreaker::new(2, Duration::from_millis(100), Duration::from_millis(500));
865
866        // First failure
867        let result: std::result::Result<(), CoreError> =
868            cb.execute(|| Err(CoreError::ComputationError(ErrorContext::new("Test error"))));
869        assert!(result.is_err());
870
871        // Second failure - should trigger circuit open
872        let result: std::result::Result<(), CoreError> =
873            cb.execute(|| Err(CoreError::ComputationError(ErrorContext::new("Test error"))));
874        assert!(result.is_err());
875
876        // Third attempt - should be blocked by circuit breaker
877        let result = cb.execute(|| Ok(()));
878        assert!(result.is_err());
879
880        let status = cb.status();
881        assert_eq!(status.state, CircuitState::Open);
882    }
883
884    #[test]
885    fn test_recovery_retry_executor() {
886        let executor = RetryExecutor::new(RecoveryStrategy::LinearBackoff {
887            max_attempts: 3,
888            delay: Duration::from_millis(1),
889        });
890
891        let mut attempt_count = 0;
892        let result = executor.execute(|| {
893            attempt_count += 1;
894            if attempt_count < 3 {
895                Err(CoreError::ComputationError(ErrorContext::new("Test error")))
896            } else {
897                Ok(42)
898            }
899        });
900
901        assert_eq!(result.expect("Operation failed"), 42);
902        assert_eq!(attempt_count, 3);
903    }
904
905    #[test]
906    fn testerror_aggregator() {
907        let mut aggregator = ErrorAggregator::new();
908
909        aggregator.add_simpleerror(CoreError::ValueError(ErrorContext::new("Error 1")));
910        aggregator.add_simpleerror(CoreError::DomainError(ErrorContext::new("Error 2")));
911
912        assert_eq!(aggregator.error_count(), 2);
913        assert!(aggregator.haserrors());
914
915        let summary = aggregator.summary();
916        assert!(summary.contains("Collected 2 error(s)"));
917    }
918}