quantrs2_device/
unified_error_handling.rs

1//! Unified Error Handling for Quantum Device Providers
2//!
3//! This module provides a comprehensive, unified error handling system for all quantum
4//! device providers, including sophisticated retry logic, error classification, and
5//! recovery strategies.
6
7use 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/// Unified error classification system for quantum device operations
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub enum ErrorCategory {
20    /// Network connectivity issues
21    Network,
22    /// Authentication and authorization errors
23    Authentication,
24    /// API rate limiting or quota exceeded
25    RateLimit,
26    /// Invalid request parameters or circuit
27    Validation,
28    /// Hardware-specific errors (calibration, decoherence, etc.)
29    Hardware,
30    /// Service temporarily unavailable
31    ServiceUnavailable,
32    /// Internal server errors from provider
33    ServerError,
34    /// Resource not found (backend, job, etc.)
35    NotFound,
36    /// Operation timed out
37    Timeout,
38    /// Insufficient permissions or credits
39    Insufficient,
40    /// Data parsing or serialization errors
41    DataFormat,
42    /// Unsupported operation for this provider/backend
43    Unsupported,
44    /// Circuit execution failed
45    Execution,
46    /// Critical system error requiring manual intervention
47    Critical,
48}
49
50/// Error severity levels for prioritization and alerting
51#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
52pub enum ErrorSeverity {
53    /// Informational - operation can continue
54    Info,
55    /// Warning - might affect performance but operation can continue
56    Warning,
57    /// Error - operation failed but system is stable
58    Error,
59    /// Critical - system stability affected, immediate attention required
60    Critical,
61}
62
63/// Recovery strategies for different error types
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub enum RecoveryStrategy {
66    /// Retry the operation immediately
67    RetryImmediate,
68    /// Retry with exponential backoff
69    RetryWithBackoff {
70        initial_delay: Duration,
71        max_delay: Duration,
72        multiplier: f64,
73        max_attempts: u32,
74    },
75    /// Switch to alternative provider/backend
76    Fallback { alternatives: Vec<String> },
77    /// Wait for a specific condition before retrying
78    WaitAndRetry {
79        wait_duration: Duration,
80        condition: String,
81    },
82    /// Circuit modification may resolve the issue
83    CircuitModification { suggestions: Vec<String> },
84    /// Manual intervention required
85    ManualIntervention {
86        instructions: String,
87        contact: String,
88    },
89    /// Operation should be abandoned
90    Abort,
91}
92
93/// Comprehensive error context with provider-specific details
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct UnifiedErrorContext {
96    /// Error category for classification
97    pub category: ErrorCategory,
98    /// Severity level for prioritization
99    pub severity: ErrorSeverity,
100    /// Provider that generated this error
101    pub provider: String,
102    /// Specific backend/device if applicable
103    pub backend: Option<String>,
104    /// Original error code from provider
105    pub error_code: Option<String>,
106    /// Human-readable error message
107    pub message: String,
108    /// Additional context and details
109    pub details: HashMap<String, String>,
110    /// Timestamp when error occurred
111    pub timestamp: std::time::SystemTime,
112    /// Suggested recovery strategy
113    pub recovery_strategy: RecoveryStrategy,
114    /// Whether this error can be retried
115    pub retryable: bool,
116    /// Request ID for tracking
117    pub request_id: Option<String>,
118    /// User-friendly explanation of what went wrong
119    pub user_message: String,
120    /// Actionable steps the user can take
121    pub suggested_actions: Vec<String>,
122}
123
124/// Enhanced device error with unified context
125#[derive(Error, Debug, Clone)]
126pub struct UnifiedDeviceError {
127    /// The underlying device error
128    pub device_error: DeviceError,
129    /// Rich error context
130    pub context: UnifiedErrorContext,
131    /// Chain of related errors (for error propagation)
132    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/// Retry configuration for different error types
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct UnifiedRetryConfig {
151    /// Maximum number of retry attempts
152    pub max_attempts: u32,
153    /// Initial delay between attempts
154    pub initial_delay: Duration,
155    /// Maximum delay between attempts
156    pub max_delay: Duration,
157    /// Backoff multiplier (exponential backoff)
158    pub backoff_multiplier: f64,
159    /// Jitter factor to avoid thundering herd (0.0 to 1.0)
160    pub jitter_factor: f64,
161    /// Timeout for individual retry attempts
162    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
178/// Comprehensive error handling and recovery system
179pub struct UnifiedErrorHandler {
180    /// Provider-specific error mappings
181    error_mappings: HashMap<String, HashMap<String, ErrorCategory>>,
182    /// Retry configurations by error category
183    retry_configs: HashMap<ErrorCategory, UnifiedRetryConfig>,
184    /// Circuit modification suggestions
185    circuit_suggestions: HashMap<ErrorCategory, Vec<String>>,
186    /// Provider fallback chains
187    fallback_chains: HashMap<String, Vec<String>>,
188    /// Error statistics for monitoring
189    error_stats: HashMap<ErrorCategory, u64>,
190}
191
192impl UnifiedErrorHandler {
193    /// Create a new unified error handler with default configurations
194    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    /// Convert a provider-specific error to a unified error
212    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        // Update statistics
223        *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, // Can be set by caller
230            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    /// Execute an operation with automatic retry and error handling
249    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                    // Apply jitter to delay
278                    let jitter = delay.as_millis() as f64 * jitter_factor;
279                    let jittered_delay = delay
280                        + Duration::from_millis(
281                            (0.5 * jitter) as u64, // Use fixed jitter instead of random for now
282                        );
283
284                    sleep(jittered_delay).await;
285
286                    // Exponential backoff
287                    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    /// Get error statistics for monitoring and alerting
301    pub fn get_error_statistics(&self) -> &HashMap<ErrorCategory, u64> {
302        &self.error_stats
303    }
304
305    /// Clear error statistics
306    pub fn clear_statistics(&mut self) {
307        self.error_stats.clear();
308    }
309
310    /// Set custom retry configuration for a specific error category
311    pub fn set_retry_config(&mut self, category: ErrorCategory, config: UnifiedRetryConfig) {
312        self.retry_configs.insert(category, config);
313    }
314
315    /// Add provider-specific error mapping
316    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    /// Set fallback provider chain
324    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    // Private helper methods
330
331    fn setup_default_mappings(&mut self) {
332        // IBM Quantum error mappings
333        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        // AWS Braket error mappings
351        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        // Azure Quantum error mappings
369        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        // Network errors - aggressive retry
384        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        // Rate limiting - gradual backoff
397        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        // Service unavailable - patient retry
410        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        // Server errors - moderate retry
423        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        // Try provider-specific mapping first
475        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        // Fallback to general classification
485        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        // Extract structured error codes if available
596        match error {
597            DeviceError::APIError(msg) => {
598                // Try to extract error codes from common formats
599                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}