1use std::collections::HashMap;
8use std::fmt::{self, Display};
9use std::time::{Duration, Instant};
10
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13use tokio::time::sleep;
14
15use crate::DeviceError;
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub enum ErrorCategory {
20 Network,
22 Authentication,
24 RateLimit,
26 Validation,
28 Hardware,
30 ServiceUnavailable,
32 ServerError,
34 NotFound,
36 Timeout,
38 Insufficient,
40 DataFormat,
42 Unsupported,
44 Execution,
46 Critical,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
52pub enum ErrorSeverity {
53 Info,
55 Warning,
57 Error,
59 Critical,
61}
62
63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub enum RecoveryStrategy {
66 RetryImmediate,
68 RetryWithBackoff {
70 initial_delay: Duration,
71 max_delay: Duration,
72 multiplier: f64,
73 max_attempts: u32,
74 },
75 Fallback { alternatives: Vec<String> },
77 WaitAndRetry {
79 wait_duration: Duration,
80 condition: String,
81 },
82 CircuitModification { suggestions: Vec<String> },
84 ManualIntervention {
86 instructions: String,
87 contact: String,
88 },
89 Abort,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct UnifiedErrorContext {
96 pub category: ErrorCategory,
98 pub severity: ErrorSeverity,
100 pub provider: String,
102 pub backend: Option<String>,
104 pub error_code: Option<String>,
106 pub message: String,
108 pub details: HashMap<String, String>,
110 pub timestamp: std::time::SystemTime,
112 pub recovery_strategy: RecoveryStrategy,
114 pub retryable: bool,
116 pub request_id: Option<String>,
118 pub user_message: String,
120 pub suggested_actions: Vec<String>,
122}
123
124#[derive(Error, Debug, Clone)]
126pub struct UnifiedDeviceError {
127 pub device_error: DeviceError,
129 pub context: UnifiedErrorContext,
131 pub error_chain: Vec<UnifiedErrorContext>,
133}
134
135impl Display for UnifiedDeviceError {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 write!(
138 f,
139 "[{}:{}] {} - {}",
140 self.context.provider,
141 self.context.category.to_string(),
142 self.context.message,
143 self.context.user_message
144 )
145 }
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct UnifiedRetryConfig {
151 pub max_attempts: u32,
153 pub initial_delay: Duration,
155 pub max_delay: Duration,
157 pub backoff_multiplier: f64,
159 pub jitter_factor: f64,
161 pub attempt_timeout: Duration,
163}
164
165impl Default for UnifiedRetryConfig {
166 fn default() -> Self {
167 Self {
168 max_attempts: 3,
169 initial_delay: Duration::from_millis(100),
170 max_delay: Duration::from_secs(30),
171 backoff_multiplier: 2.0,
172 jitter_factor: 0.1,
173 attempt_timeout: Duration::from_secs(60),
174 }
175 }
176}
177
178pub struct UnifiedErrorHandler {
180 error_mappings: HashMap<String, HashMap<String, ErrorCategory>>,
182 retry_configs: HashMap<ErrorCategory, UnifiedRetryConfig>,
184 circuit_suggestions: HashMap<ErrorCategory, Vec<String>>,
186 fallback_chains: HashMap<String, Vec<String>>,
188 error_stats: HashMap<ErrorCategory, u64>,
190}
191
192impl UnifiedErrorHandler {
193 pub fn new() -> Self {
195 let mut handler = Self {
196 error_mappings: HashMap::new(),
197 retry_configs: HashMap::new(),
198 circuit_suggestions: HashMap::new(),
199 fallback_chains: HashMap::new(),
200 error_stats: HashMap::new(),
201 };
202
203 handler.setup_default_mappings();
204 handler.setup_default_retry_configs();
205 handler.setup_circuit_suggestions();
206 handler.setup_fallback_chains();
207
208 handler
209 }
210
211 pub fn unify_error(
213 &mut self,
214 provider: &str,
215 error: DeviceError,
216 request_id: Option<String>,
217 ) -> UnifiedDeviceError {
218 let category = self.classify_error(provider, &error);
219 let severity = self.determine_severity(&category, &error);
220 let recovery_strategy = self.determine_recovery_strategy(&category, provider);
221
222 *self.error_stats.entry(category.clone()).or_insert(0) += 1;
224
225 let context = UnifiedErrorContext {
226 category: category.clone(),
227 severity,
228 provider: provider.to_string(),
229 backend: None, error_code: self.extract_error_code(&error),
231 message: error.to_string(),
232 details: self.extract_error_details(&error),
233 timestamp: std::time::SystemTime::now(),
234 recovery_strategy,
235 retryable: self.is_retryable(&category),
236 request_id,
237 user_message: self.generate_user_message(&category, &error),
238 suggested_actions: self.generate_suggested_actions(&category),
239 };
240
241 UnifiedDeviceError {
242 device_error: error,
243 context,
244 error_chain: vec![],
245 }
246 }
247
248 pub async fn execute_with_retry<F, T, E>(
250 &mut self,
251 operation: F,
252 category: ErrorCategory,
253 ) -> Result<T, UnifiedDeviceError>
254 where
255 F: Fn() -> Result<T, E>,
256 E: Into<DeviceError>,
257 {
258 let default_config = UnifiedRetryConfig::default();
259 let retry_config = self.retry_configs.get(&category).unwrap_or(&default_config);
260 let mut delay = retry_config.initial_delay;
261 let max_attempts = retry_config.max_attempts;
262 let max_delay = retry_config.max_delay;
263 let backoff_multiplier = retry_config.backoff_multiplier;
264 let jitter_factor = retry_config.jitter_factor;
265
266 for attempt in 1..=max_attempts {
267 match operation() {
268 Ok(result) => return Ok(result),
269 Err(error) => {
270 let device_error = error.into();
271 let unified_error = self.unify_error("unknown", device_error, None);
272
273 if attempt == max_attempts || !unified_error.context.retryable {
274 return Err(unified_error);
275 }
276
277 let jitter = delay.as_millis() as f64 * jitter_factor;
279 let jittered_delay = delay
280 + Duration::from_millis(
281 (0.5 * jitter) as u64, );
283
284 sleep(jittered_delay).await;
285
286 delay = std::cmp::min(
288 Duration::from_millis(
289 (delay.as_millis() as f64 * backoff_multiplier) as u64,
290 ),
291 max_delay,
292 );
293 }
294 }
295 }
296
297 unreachable!()
298 }
299
300 pub fn get_error_statistics(&self) -> &HashMap<ErrorCategory, u64> {
302 &self.error_stats
303 }
304
305 pub fn clear_statistics(&mut self) {
307 self.error_stats.clear();
308 }
309
310 pub fn set_retry_config(&mut self, category: ErrorCategory, config: UnifiedRetryConfig) {
312 self.retry_configs.insert(category, config);
313 }
314
315 pub fn add_error_mapping(&mut self, provider: &str, error_code: &str, category: ErrorCategory) {
317 self.error_mappings
318 .entry(provider.to_string())
319 .or_insert_with(HashMap::new)
320 .insert(error_code.to_string(), category);
321 }
322
323 pub fn set_fallback_chain(&mut self, primary_provider: &str, fallbacks: Vec<String>) {
325 self.fallback_chains
326 .insert(primary_provider.to_string(), fallbacks);
327 }
328
329 fn setup_default_mappings(&mut self) {
332 let mut ibm_mappings = HashMap::new();
334 ibm_mappings.insert(
335 "AUTHENTICATION_ERROR".to_string(),
336 ErrorCategory::Authentication,
337 );
338 ibm_mappings.insert("RATE_LIMIT_EXCEEDED".to_string(), ErrorCategory::RateLimit);
339 ibm_mappings.insert(
340 "BACKEND_NOT_AVAILABLE".to_string(),
341 ErrorCategory::ServiceUnavailable,
342 );
343 ibm_mappings.insert("INVALID_CIRCUIT".to_string(), ErrorCategory::Validation);
344 ibm_mappings.insert(
345 "INSUFFICIENT_CREDITS".to_string(),
346 ErrorCategory::Insufficient,
347 );
348 self.error_mappings.insert("ibm".to_string(), ibm_mappings);
349
350 let mut aws_mappings = HashMap::new();
352 aws_mappings.insert(
353 "AccessDeniedException".to_string(),
354 ErrorCategory::Authentication,
355 );
356 aws_mappings.insert("ThrottlingException".to_string(), ErrorCategory::RateLimit);
357 aws_mappings.insert(
358 "ServiceUnavailableException".to_string(),
359 ErrorCategory::ServiceUnavailable,
360 );
361 aws_mappings.insert("ValidationException".to_string(), ErrorCategory::Validation);
362 aws_mappings.insert(
363 "DeviceOfflineException".to_string(),
364 ErrorCategory::Hardware,
365 );
366 self.error_mappings.insert("aws".to_string(), aws_mappings);
367
368 let mut azure_mappings = HashMap::new();
370 azure_mappings.insert("Unauthorized".to_string(), ErrorCategory::Authentication);
371 azure_mappings.insert("TooManyRequests".to_string(), ErrorCategory::RateLimit);
372 azure_mappings.insert(
373 "ServiceUnavailable".to_string(),
374 ErrorCategory::ServiceUnavailable,
375 );
376 azure_mappings.insert("BadRequest".to_string(), ErrorCategory::Validation);
377 azure_mappings.insert("InsufficientQuota".to_string(), ErrorCategory::Insufficient);
378 self.error_mappings
379 .insert("azure".to_string(), azure_mappings);
380 }
381
382 fn setup_default_retry_configs(&mut self) {
383 self.retry_configs.insert(
385 ErrorCategory::Network,
386 UnifiedRetryConfig {
387 max_attempts: 5,
388 initial_delay: Duration::from_millis(50),
389 max_delay: Duration::from_secs(10),
390 backoff_multiplier: 2.0,
391 jitter_factor: 0.2,
392 attempt_timeout: Duration::from_secs(30),
393 },
394 );
395
396 self.retry_configs.insert(
398 ErrorCategory::RateLimit,
399 UnifiedRetryConfig {
400 max_attempts: 3,
401 initial_delay: Duration::from_secs(1),
402 max_delay: Duration::from_secs(60),
403 backoff_multiplier: 3.0,
404 jitter_factor: 0.3,
405 attempt_timeout: Duration::from_secs(120),
406 },
407 );
408
409 self.retry_configs.insert(
411 ErrorCategory::ServiceUnavailable,
412 UnifiedRetryConfig {
413 max_attempts: 3,
414 initial_delay: Duration::from_secs(5),
415 max_delay: Duration::from_secs(120),
416 backoff_multiplier: 2.5,
417 jitter_factor: 0.4,
418 attempt_timeout: Duration::from_secs(180),
419 },
420 );
421
422 self.retry_configs.insert(
424 ErrorCategory::ServerError,
425 UnifiedRetryConfig {
426 max_attempts: 3,
427 initial_delay: Duration::from_millis(500),
428 max_delay: Duration::from_secs(30),
429 backoff_multiplier: 2.0,
430 jitter_factor: 0.15,
431 attempt_timeout: Duration::from_secs(60),
432 },
433 );
434 }
435
436 fn setup_circuit_suggestions(&mut self) {
437 self.circuit_suggestions.insert(
438 ErrorCategory::Hardware,
439 vec![
440 "Reduce circuit depth to minimize decoherence effects".to_string(),
441 "Add error mitigation techniques like ZNE or PEC".to_string(),
442 "Use native gates for the target hardware".to_string(),
443 "Implement dynamical decoupling sequences".to_string(),
444 ],
445 );
446
447 self.circuit_suggestions.insert(
448 ErrorCategory::Validation,
449 vec![
450 "Check circuit connectivity matches hardware topology".to_string(),
451 "Verify all gates are supported by the target backend".to_string(),
452 "Ensure qubit indices are within hardware limits".to_string(),
453 "Add necessary SWAP gates for qubit routing".to_string(),
454 ],
455 );
456 }
457
458 fn setup_fallback_chains(&mut self) {
459 self.fallback_chains.insert(
460 "ibm".to_string(),
461 vec!["aws".to_string(), "azure".to_string()],
462 );
463 self.fallback_chains.insert(
464 "aws".to_string(),
465 vec!["ibm".to_string(), "azure".to_string()],
466 );
467 self.fallback_chains.insert(
468 "azure".to_string(),
469 vec!["ibm".to_string(), "aws".to_string()],
470 );
471 }
472
473 fn classify_error(&self, provider: &str, error: &DeviceError) -> ErrorCategory {
474 if let Some(provider_mappings) = self.error_mappings.get(provider) {
476 let error_string = error.to_string();
477 for (error_code, category) in provider_mappings {
478 if error_string.contains(error_code) {
479 return category.clone();
480 }
481 }
482 }
483
484 match error {
486 DeviceError::ExecutionFailed(_) => ErrorCategory::Execution,
487 DeviceError::Connection(_) => ErrorCategory::Network,
488 DeviceError::Authentication(_) => ErrorCategory::Authentication,
489 DeviceError::APIError(msg) => {
490 if msg.contains("rate limit") || msg.contains("quota") {
491 ErrorCategory::RateLimit
492 } else if msg.contains("unavailable") || msg.contains("maintenance") {
493 ErrorCategory::ServiceUnavailable
494 } else if msg.contains("timeout") {
495 ErrorCategory::Timeout
496 } else {
497 ErrorCategory::ServerError
498 }
499 }
500 DeviceError::Deserialization(_) => ErrorCategory::DataFormat,
501 DeviceError::UnsupportedDevice(_) => ErrorCategory::Unsupported,
502 DeviceError::UnsupportedOperation(_) => ErrorCategory::Unsupported,
503 DeviceError::InvalidInput(_) => ErrorCategory::Validation,
504 DeviceError::CircuitConversion(_) => ErrorCategory::Validation,
505 DeviceError::InsufficientQubits { .. } => ErrorCategory::Hardware,
506 DeviceError::RoutingError(_) => ErrorCategory::Hardware,
507 DeviceError::OptimizationError(_) => ErrorCategory::ServerError,
508 DeviceError::GraphAnalysisError(_) => ErrorCategory::ServerError,
509 DeviceError::NotImplemented(_) => ErrorCategory::Unsupported,
510 DeviceError::InvalidMapping(_) => ErrorCategory::Validation,
511 DeviceError::DeviceNotFound(_) => ErrorCategory::NotFound,
512 DeviceError::JobSubmission(_) => ErrorCategory::ServerError,
513 DeviceError::JobExecution(_) => ErrorCategory::Hardware,
514 DeviceError::Timeout(_) => ErrorCategory::Timeout,
515 DeviceError::DeviceNotInitialized(_) => ErrorCategory::Hardware,
516 DeviceError::JobExecutionFailed(_) => ErrorCategory::Hardware,
517 DeviceError::InvalidResponse(_) => ErrorCategory::DataFormat,
518 DeviceError::UnknownJobStatus(_) => ErrorCategory::ServerError,
519 DeviceError::ResourceExhaustion(_) => ErrorCategory::Hardware,
520 }
521 }
522
523 fn determine_severity(&self, category: &ErrorCategory, _error: &DeviceError) -> ErrorSeverity {
524 match category {
525 ErrorCategory::Critical => ErrorSeverity::Critical,
526 ErrorCategory::Authentication
527 | ErrorCategory::Hardware
528 | ErrorCategory::ServiceUnavailable => ErrorSeverity::Error,
529 ErrorCategory::RateLimit | ErrorCategory::Timeout | ErrorCategory::Network => {
530 ErrorSeverity::Warning
531 }
532 _ => ErrorSeverity::Info,
533 }
534 }
535
536 fn determine_recovery_strategy(
537 &self,
538 category: &ErrorCategory,
539 provider: &str,
540 ) -> RecoveryStrategy {
541 match category {
542 ErrorCategory::Network | ErrorCategory::Timeout => RecoveryStrategy::RetryWithBackoff {
543 initial_delay: Duration::from_millis(100),
544 max_delay: Duration::from_secs(10),
545 multiplier: 2.0,
546 max_attempts: 5,
547 },
548 ErrorCategory::RateLimit => RecoveryStrategy::WaitAndRetry {
549 wait_duration: Duration::from_secs(60),
550 condition: "Rate limit reset".to_string(),
551 },
552 ErrorCategory::ServiceUnavailable => {
553 if let Some(fallbacks) = self.fallback_chains.get(provider) {
554 RecoveryStrategy::Fallback {
555 alternatives: fallbacks.clone(),
556 }
557 } else {
558 RecoveryStrategy::WaitAndRetry {
559 wait_duration: Duration::from_secs(300),
560 condition: "Service restoration".to_string(),
561 }
562 }
563 }
564 ErrorCategory::Validation | ErrorCategory::Hardware => {
565 if let Some(suggestions) = self.circuit_suggestions.get(category) {
566 RecoveryStrategy::CircuitModification {
567 suggestions: suggestions.clone(),
568 }
569 } else {
570 RecoveryStrategy::Abort
571 }
572 }
573 ErrorCategory::Authentication | ErrorCategory::Insufficient => {
574 RecoveryStrategy::ManualIntervention {
575 instructions: "Check credentials and account status".to_string(),
576 contact: "support@quantumcloud.com".to_string(),
577 }
578 }
579 _ => RecoveryStrategy::Abort,
580 }
581 }
582
583 fn is_retryable(&self, category: &ErrorCategory) -> bool {
584 matches!(
585 category,
586 ErrorCategory::Network
587 | ErrorCategory::RateLimit
588 | ErrorCategory::ServiceUnavailable
589 | ErrorCategory::ServerError
590 | ErrorCategory::Timeout
591 )
592 }
593
594 fn extract_error_code(&self, error: &DeviceError) -> Option<String> {
595 match error {
597 DeviceError::APIError(msg) => {
598 if let Some(start) = msg.find("Error:") {
600 if let Some(end) = msg[start..].find(" ") {
601 return Some(msg[start + 6..start + end].to_string());
602 }
603 }
604 None
605 }
606 _ => None,
607 }
608 }
609
610 fn extract_error_details(&self, error: &DeviceError) -> HashMap<String, String> {
611 let mut details = HashMap::new();
612 details.insert("error_type".to_string(), format!("{:?}", error));
613 details.insert("error_message".to_string(), error.to_string());
614 details
615 }
616
617 fn generate_user_message(&self, category: &ErrorCategory, _error: &DeviceError) -> String {
618 match category {
619 ErrorCategory::Network => {
620 "Network connectivity issue. Please check your internet connection.".to_string()
621 }
622 ErrorCategory::Authentication => {
623 "Authentication failed. Please verify your credentials.".to_string()
624 }
625 ErrorCategory::RateLimit => {
626 "Rate limit exceeded. Please wait before making more requests.".to_string()
627 }
628 ErrorCategory::Validation => {
629 "Invalid request. Please check your circuit and parameters.".to_string()
630 }
631 ErrorCategory::Hardware => {
632 "Hardware issue detected. The quantum device may be calibrating.".to_string()
633 }
634 ErrorCategory::ServiceUnavailable => {
635 "Quantum service temporarily unavailable. Please try again later.".to_string()
636 }
637 ErrorCategory::ServerError => {
638 "Internal server error. The issue has been reported to our team.".to_string()
639 }
640 ErrorCategory::NotFound => {
641 "Requested resource not found. Please check the identifier.".to_string()
642 }
643 ErrorCategory::Timeout => {
644 "Operation timed out. Please try again or reduce complexity.".to_string()
645 }
646 ErrorCategory::Insufficient => {
647 "Insufficient resources or credits. Please check your account.".to_string()
648 }
649 ErrorCategory::DataFormat => {
650 "Data format error. Please check your input data.".to_string()
651 }
652 ErrorCategory::Unsupported => "Operation not supported on this platform.".to_string(),
653 ErrorCategory::Execution => {
654 "Circuit execution failed. Please check your circuit and try again.".to_string()
655 }
656 ErrorCategory::Critical => {
657 "Critical system error. Please contact support immediately.".to_string()
658 }
659 }
660 }
661
662 fn generate_suggested_actions(&self, category: &ErrorCategory) -> Vec<String> {
663 match category {
664 ErrorCategory::Network => vec![
665 "Check internet connectivity".to_string(),
666 "Try again in a few moments".to_string(),
667 "Switch to a different network".to_string(),
668 ],
669 ErrorCategory::Authentication => vec![
670 "Verify API credentials".to_string(),
671 "Check token expiration".to_string(),
672 "Refresh authentication".to_string(),
673 ],
674 ErrorCategory::RateLimit => vec![
675 "Wait before retrying".to_string(),
676 "Reduce request frequency".to_string(),
677 "Implement request batching".to_string(),
678 ],
679 ErrorCategory::Validation => vec![
680 "Review circuit structure".to_string(),
681 "Check parameter ranges".to_string(),
682 "Validate against backend specifications".to_string(),
683 ],
684 ErrorCategory::Hardware => vec![
685 "Try a different backend".to_string(),
686 "Reduce circuit complexity".to_string(),
687 "Wait for calibration to complete".to_string(),
688 ],
689 _ => vec![
690 "Try again later".to_string(),
691 "Contact support if issue persists".to_string(),
692 ],
693 }
694 }
695}
696
697impl Default for UnifiedErrorHandler {
698 fn default() -> Self {
699 Self::new()
700 }
701}
702
703impl Display for ErrorCategory {
704 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
705 let display = match self {
706 ErrorCategory::Network => "Network",
707 ErrorCategory::Authentication => "Authentication",
708 ErrorCategory::RateLimit => "RateLimit",
709 ErrorCategory::Validation => "Validation",
710 ErrorCategory::Hardware => "Hardware",
711 ErrorCategory::ServiceUnavailable => "ServiceUnavailable",
712 ErrorCategory::ServerError => "ServerError",
713 ErrorCategory::NotFound => "NotFound",
714 ErrorCategory::Timeout => "Timeout",
715 ErrorCategory::Insufficient => "Insufficient",
716 ErrorCategory::DataFormat => "DataFormat",
717 ErrorCategory::Unsupported => "Unsupported",
718 ErrorCategory::Execution => "Execution",
719 ErrorCategory::Critical => "Critical",
720 };
721 write!(f, "{}", display)
722 }
723}
724
725#[cfg(test)]
726mod tests {
727 use super::*;
728
729 #[test]
730 fn test_error_handler_creation() {
731 let handler = UnifiedErrorHandler::new();
732 assert!(!handler.error_mappings.is_empty());
733 assert!(!handler.retry_configs.is_empty());
734 }
735
736 #[test]
737 fn test_error_classification() {
738 let mut handler = UnifiedErrorHandler::new();
739 let error = DeviceError::Connection("Network timeout".to_string());
740 let unified_error = handler.unify_error("ibm", error, None);
741
742 assert_eq!(unified_error.context.category, ErrorCategory::Network);
743 assert_eq!(unified_error.context.provider, "ibm");
744 assert!(unified_error.context.retryable);
745 }
746
747 #[test]
748 fn test_retry_config() {
749 let config = UnifiedRetryConfig::default();
750 assert_eq!(config.max_attempts, 3);
751 assert!(config.initial_delay > Duration::ZERO);
752 }
753
754 #[test]
755 fn test_error_statistics() {
756 let mut handler = UnifiedErrorHandler::new();
757 let error = DeviceError::APIError("Test error".to_string());
758 handler.unify_error("test", error, None);
759
760 let stats = handler.get_error_statistics();
761 assert!(stats.len() > 0);
762 }
763}