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,
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 const 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_default()
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            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        // Extract structured error codes if available
601        match error {
602            DeviceError::APIError(msg) => {
603                // Try to extract error codes from common formats
604                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}