1use std::fmt;
11use std::sync::{Arc, Mutex};
12use std::time::{Duration, Instant};
13
14use crate::error::{CoreError, CoreResult, ErrorContext};
15
16#[derive(Debug, Clone)]
18pub enum RecoveryStrategy {
19 FailFast,
21 ExponentialBackoff {
23 max_attempts: usize,
24 initialdelay: Duration,
25 maxdelay: Duration,
26 multiplier: f64,
27 },
28 LinearBackoff {
30 max_attempts: usize,
31 delay: Duration,
32 },
33 CustomBackoff {
35 max_attempts: usize,
36 delays: Vec<Duration>,
37 },
38 CircuitBreaker {
40 failure_threshold: usize,
41 timeout: Duration,
42 recoverytimeout: Duration,
43 },
44 Fallback,
46 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#[derive(Debug, Clone)]
63pub struct RecoveryHint {
64 pub action: String,
66 pub explanation: String,
68 pub examples: Vec<String>,
70 pub confidence: f64,
72}
73
74impl RecoveryHint {
75 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 pub fn with_example<S: Into<String>>(mut self, example: S) -> Self {
87 self.examples.push(example.into());
88 self
89 }
90
91 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#[derive(Debug, Clone)]
125pub struct RecoverableError {
126 pub error: CoreError,
128 pub strategy: RecoveryStrategy,
130 pub hints: Vec<RecoveryHint>,
132 pub retryable: bool,
134 pub severity: ErrorSeverity,
136 pub metadata: std::collections::HashMap<String, String>,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
142pub enum ErrorSeverity {
143 Info,
145 Warning,
147 Error,
149 Critical,
151 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 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 pub fn with_strategy(mut self, strategy: RecoveryStrategy) -> Self {
184 self.strategy = strategy;
185 self
186 }
187
188 pub fn with_hint(mut self, hint: RecoveryHint) -> Self {
190 self.hints.push(hint);
191 self
192 }
193
194 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 pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
206 self.severity = severity;
207 self
208 }
209
210 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 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#[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 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 pub fn new(failure_threshold: usize, timeout: Duration, recoverytimeout: Duration) -> Self {
467 Self::timeout(failure_threshold, timeout, recoverytimeout)
468 }
469
470 pub fn execute<F, T>(&self, f: F) -> CoreResult<T>
472 where
473 F: FnOnce() -> CoreResult<T>,
474 {
475 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 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 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#[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#[derive(Debug)]
565pub struct RetryExecutor {
566 strategy: RecoveryStrategy,
567}
568
569impl RetryExecutor {
570 pub fn strategy(strategy: RecoveryStrategy) -> Self {
572 Self { strategy }
573 }
574
575 pub fn new(strategy: RecoveryStrategy) -> Self {
577 Self::strategy(strategy)
578 }
579
580 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(), }
667 }
668}
669
670#[derive(Debug, Default)]
672pub struct ErrorAggregator {
673 errors: Vec<RecoverableError>,
674 maxerrors: Option<usize>,
675}
676
677impl ErrorAggregator {
678 pub fn new() -> Self {
680 Self::default()
681 }
682
683 pub fn errors(maxerrors: usize) -> Self {
685 Self {
686 errors: Vec::new(),
687 maxerrors: Some(maxerrors),
688 }
689 }
690
691 pub fn adderror(&mut self, error: RecoverableError) {
693 if let Some(max) = self.maxerrors {
694 if self.errors.len() >= max {
695 return; }
697 }
698
699 self.errors.push(error);
700 }
701
702 pub fn add_simpleerror(&mut self, error: CoreError) {
704 self.adderror(RecoverableError::error(error));
705 }
706
707 pub fn haserrors(&self) -> bool {
709 !self.errors.is_empty()
710 }
711
712 pub fn error_count(&self) -> usize {
714 self.errors.len()
715 }
716
717 pub fn errors_2(&self) -> &[RecoverableError] {
719 &self.errors
720 }
721
722 pub fn most_severeerror(&self) -> Option<&RecoverableError> {
724 self.errors.iter().max_by_key(|err| err.severity)
725 }
726
727 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 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 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
775pub mod hints {
777 use super::RecoveryHint;
778
779 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 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 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 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 let result: std::result::Result<(), CoreError> =
868 cb.execute(|| Err(CoreError::ComputationError(ErrorContext::new("Test error"))));
869 assert!(result.is_err());
870
871 let result: std::result::Result<(), CoreError> =
873 cb.execute(|| Err(CoreError::ComputationError(ErrorContext::new("Test error"))));
874 assert!(result.is_err());
875
876 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}