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,
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 const 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_default()
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 DeviceError::LockError(_) => ErrorCategory::Critical,
521 }
522 }
523
524 const fn determine_severity(
525 &self,
526 category: &ErrorCategory,
527 _error: &DeviceError,
528 ) -> ErrorSeverity {
529 match category {
530 ErrorCategory::Critical => ErrorSeverity::Critical,
531 ErrorCategory::Authentication
532 | ErrorCategory::Hardware
533 | ErrorCategory::ServiceUnavailable => ErrorSeverity::Error,
534 ErrorCategory::RateLimit | ErrorCategory::Timeout | ErrorCategory::Network => {
535 ErrorSeverity::Warning
536 }
537 _ => ErrorSeverity::Info,
538 }
539 }
540
541 fn determine_recovery_strategy(
542 &self,
543 category: &ErrorCategory,
544 provider: &str,
545 ) -> RecoveryStrategy {
546 match category {
547 ErrorCategory::Network | ErrorCategory::Timeout => RecoveryStrategy::RetryWithBackoff {
548 initial_delay: Duration::from_millis(100),
549 max_delay: Duration::from_secs(10),
550 multiplier: 2.0,
551 max_attempts: 5,
552 },
553 ErrorCategory::RateLimit => RecoveryStrategy::WaitAndRetry {
554 wait_duration: Duration::from_secs(60),
555 condition: "Rate limit reset".to_string(),
556 },
557 ErrorCategory::ServiceUnavailable => {
558 if let Some(fallbacks) = self.fallback_chains.get(provider) {
559 RecoveryStrategy::Fallback {
560 alternatives: fallbacks.clone(),
561 }
562 } else {
563 RecoveryStrategy::WaitAndRetry {
564 wait_duration: Duration::from_secs(300),
565 condition: "Service restoration".to_string(),
566 }
567 }
568 }
569 ErrorCategory::Validation | ErrorCategory::Hardware => {
570 if let Some(suggestions) = self.circuit_suggestions.get(category) {
571 RecoveryStrategy::CircuitModification {
572 suggestions: suggestions.clone(),
573 }
574 } else {
575 RecoveryStrategy::Abort
576 }
577 }
578 ErrorCategory::Authentication | ErrorCategory::Insufficient => {
579 RecoveryStrategy::ManualIntervention {
580 instructions: "Check credentials and account status".to_string(),
581 contact: "support@quantumcloud.com".to_string(),
582 }
583 }
584 _ => RecoveryStrategy::Abort,
585 }
586 }
587
588 const fn is_retryable(&self, category: &ErrorCategory) -> bool {
589 matches!(
590 category,
591 ErrorCategory::Network
592 | ErrorCategory::RateLimit
593 | ErrorCategory::ServiceUnavailable
594 | ErrorCategory::ServerError
595 | ErrorCategory::Timeout
596 )
597 }
598
599 fn extract_error_code(&self, error: &DeviceError) -> Option<String> {
600 match error {
602 DeviceError::APIError(msg) => {
603 if let Some(start) = msg.find("Error:") {
605 if let Some(end) = msg[start..].find(' ') {
606 return Some(msg[start + 6..start + end].to_string());
607 }
608 }
609 None
610 }
611 _ => None,
612 }
613 }
614
615 fn extract_error_details(&self, error: &DeviceError) -> HashMap<String, String> {
616 let mut details = HashMap::new();
617 details.insert("error_type".to_string(), format!("{error:?}"));
618 details.insert("error_message".to_string(), error.to_string());
619 details
620 }
621
622 fn generate_user_message(&self, category: &ErrorCategory, _error: &DeviceError) -> String {
623 match category {
624 ErrorCategory::Network => {
625 "Network connectivity issue. Please check your internet connection.".to_string()
626 }
627 ErrorCategory::Authentication => {
628 "Authentication failed. Please verify your credentials.".to_string()
629 }
630 ErrorCategory::RateLimit => {
631 "Rate limit exceeded. Please wait before making more requests.".to_string()
632 }
633 ErrorCategory::Validation => {
634 "Invalid request. Please check your circuit and parameters.".to_string()
635 }
636 ErrorCategory::Hardware => {
637 "Hardware issue detected. The quantum device may be calibrating.".to_string()
638 }
639 ErrorCategory::ServiceUnavailable => {
640 "Quantum service temporarily unavailable. Please try again later.".to_string()
641 }
642 ErrorCategory::ServerError => {
643 "Internal server error. The issue has been reported to our team.".to_string()
644 }
645 ErrorCategory::NotFound => {
646 "Requested resource not found. Please check the identifier.".to_string()
647 }
648 ErrorCategory::Timeout => {
649 "Operation timed out. Please try again or reduce complexity.".to_string()
650 }
651 ErrorCategory::Insufficient => {
652 "Insufficient resources or credits. Please check your account.".to_string()
653 }
654 ErrorCategory::DataFormat => {
655 "Data format error. Please check your input data.".to_string()
656 }
657 ErrorCategory::Unsupported => "Operation not supported on this platform.".to_string(),
658 ErrorCategory::Execution => {
659 "Circuit execution failed. Please check your circuit and try again.".to_string()
660 }
661 ErrorCategory::Critical => {
662 "Critical system error. Please contact support immediately.".to_string()
663 }
664 }
665 }
666
667 fn generate_suggested_actions(&self, category: &ErrorCategory) -> Vec<String> {
668 match category {
669 ErrorCategory::Network => vec![
670 "Check internet connectivity".to_string(),
671 "Try again in a few moments".to_string(),
672 "Switch to a different network".to_string(),
673 ],
674 ErrorCategory::Authentication => vec![
675 "Verify API credentials".to_string(),
676 "Check token expiration".to_string(),
677 "Refresh authentication".to_string(),
678 ],
679 ErrorCategory::RateLimit => vec![
680 "Wait before retrying".to_string(),
681 "Reduce request frequency".to_string(),
682 "Implement request batching".to_string(),
683 ],
684 ErrorCategory::Validation => vec![
685 "Review circuit structure".to_string(),
686 "Check parameter ranges".to_string(),
687 "Validate against backend specifications".to_string(),
688 ],
689 ErrorCategory::Hardware => vec![
690 "Try a different backend".to_string(),
691 "Reduce circuit complexity".to_string(),
692 "Wait for calibration to complete".to_string(),
693 ],
694 _ => vec![
695 "Try again later".to_string(),
696 "Contact support if issue persists".to_string(),
697 ],
698 }
699 }
700}
701
702impl Default for UnifiedErrorHandler {
703 fn default() -> Self {
704 Self::new()
705 }
706}
707
708impl Display for ErrorCategory {
709 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
710 let display = match self {
711 Self::Network => "Network",
712 Self::Authentication => "Authentication",
713 Self::RateLimit => "RateLimit",
714 Self::Validation => "Validation",
715 Self::Hardware => "Hardware",
716 Self::ServiceUnavailable => "ServiceUnavailable",
717 Self::ServerError => "ServerError",
718 Self::NotFound => "NotFound",
719 Self::Timeout => "Timeout",
720 Self::Insufficient => "Insufficient",
721 Self::DataFormat => "DataFormat",
722 Self::Unsupported => "Unsupported",
723 Self::Execution => "Execution",
724 Self::Critical => "Critical",
725 };
726 write!(f, "{display}")
727 }
728}
729
730#[cfg(test)]
731mod tests {
732 use super::*;
733
734 #[test]
735 fn test_error_handler_creation() {
736 let handler = UnifiedErrorHandler::new();
737 assert!(!handler.error_mappings.is_empty());
738 assert!(!handler.retry_configs.is_empty());
739 }
740
741 #[test]
742 fn test_error_classification() {
743 let mut handler = UnifiedErrorHandler::new();
744 let error = DeviceError::Connection("Network timeout".to_string());
745 let unified_error = handler.unify_error("ibm", error, None);
746
747 assert_eq!(unified_error.context.category, ErrorCategory::Network);
748 assert_eq!(unified_error.context.provider, "ibm");
749 assert!(unified_error.context.retryable);
750 }
751
752 #[test]
753 fn test_retry_config() {
754 let config = UnifiedRetryConfig::default();
755 assert_eq!(config.max_attempts, 3);
756 assert!(config.initial_delay > Duration::ZERO);
757 }
758
759 #[test]
760 fn test_error_statistics() {
761 let mut handler = UnifiedErrorHandler::new();
762 let error = DeviceError::APIError("Test error".to_string());
763 handler.unify_error("test", error, None);
764
765 let stats = handler.get_error_statistics();
766 assert!(stats.len() > 0);
767 }
768}