server/service_bus_manager/
errors.rs

1//! Error types for Azure Service Bus operations.
2//!
3//! This module provides comprehensive error handling for all Service Bus operations,
4//! including Azure API errors, connection issues, message operations, and bulk operations.
5//! The error types are designed to provide detailed context for debugging and user feedback.
6
7use crate::common::{CacheError, HttpError};
8use std::fmt;
9
10/// Comprehensive error type for all Service Bus operations.
11///
12/// Provides detailed error information with appropriate context for debugging
13/// and user feedback. Includes specialized variants for different operation types
14/// and Azure API integration.
15///
16/// # Error Categories
17///
18/// - **Azure API Errors** - Detailed Azure service errors with request tracking
19/// - **Connection Errors** - Authentication and connection issues
20/// - **Consumer/Producer Errors** - Client creation and management errors
21/// - **Message Operation Errors** - Message handling failures
22/// - **Bulk Operation Errors** - Batch operation failures with partial success tracking
23/// - **Queue Errors** - Queue management and navigation errors
24/// - **Configuration Errors** - Invalid configuration or setup issues
25///
26/// # Examples
27///
28/// ```no_run
29/// use quetty_server::service_bus_manager::{ServiceBusError, ServiceBusResult};
30///
31/// fn handle_error(error: ServiceBusError) {
32///     match error {
33///         ServiceBusError::QueueNotFound(queue) => {
34///             eprintln!("Queue '{}' does not exist", queue);
35///         }
36///         ServiceBusError::AzureApiError { code, message, .. } => {
37///             eprintln!("Azure API error {}: {}", code, message);
38///         }
39///         _ => eprintln!("Service Bus error: {}", error),
40///     }
41/// }
42/// ```
43#[derive(Debug, Clone)]
44pub enum ServiceBusError {
45    /// Azure API specific errors with full context for debugging and support.
46    ///
47    /// Contains detailed information about Azure service errors including
48    /// error codes, HTTP status, and request tracking information.
49    AzureApiError {
50        /// Azure error code (e.g., "SubscriptionNotFound", "Unauthorized")
51        code: String,
52        /// HTTP status code from the API response
53        status_code: u16,
54        /// Human-readable error message from Azure
55        message: String,
56        /// Azure request ID for tracking and support
57        request_id: Option<String>,
58        /// Operation that failed (e.g., "list_subscriptions", "send_message")
59        operation: String,
60    },
61
62    /// Connection establishment failed
63    ConnectionFailed(String),
64    /// Existing connection was lost during operation
65    ConnectionLost(String),
66    /// Authentication process failed
67    AuthenticationFailed(String),
68    /// Authentication configuration or credential error
69    AuthenticationError(String),
70
71    /// Message consumer creation failed
72    ConsumerCreationFailed(String),
73    /// No consumer found for the current context
74    ConsumerNotFound,
75    /// Consumer already exists for the specified queue
76    ConsumerAlreadyExists(String),
77
78    /// Message producer creation failed
79    ProducerCreationFailed(String),
80    /// No producer found for the specified queue
81    ProducerNotFound(String),
82
83    /// Message receive operation failed
84    MessageReceiveFailed(String),
85    /// Message send operation failed
86    MessageSendFailed(String),
87    /// Message completion failed
88    MessageCompleteFailed(String),
89    /// Message abandon operation failed
90    MessageAbandonFailed(String),
91    /// Message dead letter operation failed
92    MessageDeadLetterFailed(String),
93
94    /// Bulk operation failed completely
95    BulkOperationFailed(String),
96    /// Bulk operation partially failed with detailed results
97    BulkOperationPartialFailure {
98        /// Number of successful operations
99        successful: usize,
100        /// Number of failed operations
101        failed: usize,
102        /// Detailed error messages for failed operations
103        errors: Vec<String>,
104    },
105
106    /// Specified queue does not exist
107    QueueNotFound(String),
108    /// Failed to switch to the specified queue
109    QueueSwitchFailed(String),
110    /// Queue name format is invalid
111    InvalidQueueName(String),
112
113    /// Configuration value is missing or invalid
114    ConfigurationError(String),
115    /// Configuration format or structure is invalid
116    InvalidConfiguration(String),
117
118    /// Operation exceeded timeout limit
119    OperationTimeout(String),
120
121    /// Internal service error
122    InternalError(String),
123    /// Unknown or unexpected error
124    Unknown(String),
125}
126
127impl fmt::Display for ServiceBusError {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        match self {
130            ServiceBusError::AzureApiError {
131                code,
132                status_code,
133                message,
134                request_id,
135                operation,
136            } => {
137                write!(
138                    f,
139                    "Azure API error during {operation}: {code} (HTTP {status_code}) - {message}"
140                )?;
141                if let Some(req_id) = request_id {
142                    write!(f, " [Request ID: {req_id}]")?;
143                }
144                Ok(())
145            }
146            ServiceBusError::ConnectionFailed(msg) => write!(f, "Connection failed: {msg}"),
147            ServiceBusError::ConnectionLost(msg) => write!(f, "Connection lost: {msg}"),
148            ServiceBusError::AuthenticationFailed(msg) => {
149                write!(f, "Authentication failed: {msg}")
150            }
151            ServiceBusError::AuthenticationError(msg) => {
152                write!(f, "Authentication error: {msg}")
153            }
154
155            ServiceBusError::ConsumerCreationFailed(msg) => {
156                write!(f, "Consumer creation failed: {msg}")
157            }
158            ServiceBusError::ConsumerNotFound => write!(f, "Consumer not found"),
159            ServiceBusError::ConsumerAlreadyExists(queue) => {
160                write!(f, "Consumer already exists for queue: {queue}")
161            }
162
163            ServiceBusError::ProducerCreationFailed(msg) => {
164                write!(f, "Producer creation failed: {msg}")
165            }
166            ServiceBusError::ProducerNotFound(queue) => {
167                write!(f, "Producer not found for queue: {queue}")
168            }
169
170            ServiceBusError::MessageReceiveFailed(msg) => {
171                write!(f, "Message receive failed: {msg}")
172            }
173            ServiceBusError::MessageSendFailed(msg) => write!(f, "Message send failed: {msg}"),
174            ServiceBusError::MessageCompleteFailed(msg) => {
175                write!(f, "Message complete failed: {msg}")
176            }
177            ServiceBusError::MessageAbandonFailed(msg) => {
178                write!(f, "Message abandon failed: {msg}")
179            }
180            ServiceBusError::MessageDeadLetterFailed(msg) => {
181                write!(f, "Message dead letter failed: {msg}")
182            }
183
184            ServiceBusError::BulkOperationFailed(msg) => {
185                write!(f, "Bulk operation failed: {msg}")
186            }
187            ServiceBusError::BulkOperationPartialFailure {
188                successful,
189                failed,
190                errors,
191            } => {
192                write!(
193                    f,
194                    "Bulk operation partially failed: {} successful, {} failed. Errors: {}",
195                    successful,
196                    failed,
197                    errors.join("; ")
198                )
199            }
200
201            ServiceBusError::QueueNotFound(queue) => write!(f, "Queue not found: {queue}"),
202            ServiceBusError::QueueSwitchFailed(msg) => write!(f, "Queue switch failed: {msg}"),
203            ServiceBusError::InvalidQueueName(queue) => write!(f, "Invalid queue name: {queue}"),
204
205            ServiceBusError::ConfigurationError(msg) => write!(f, "Configuration error: {msg}"),
206            ServiceBusError::InvalidConfiguration(msg) => {
207                write!(f, "Invalid configuration: {msg}")
208            }
209
210            ServiceBusError::OperationTimeout(msg) => write!(f, "Operation timeout: {msg}"),
211
212            ServiceBusError::InternalError(msg) => write!(f, "Internal error: {msg}"),
213            ServiceBusError::Unknown(msg) => write!(f, "Unknown error: {msg}"),
214        }
215    }
216}
217
218impl std::error::Error for ServiceBusError {}
219
220impl ServiceBusError {
221    /// Creates an Azure API error with full context.
222    ///
223    /// # Arguments
224    ///
225    /// * `operation` - The operation that failed (e.g., "list_queues")
226    /// * `code` - Azure error code (e.g., "Unauthorized")
227    /// * `status_code` - HTTP status code from the response
228    /// * `message` - Human-readable error message
229    ///
230    /// # Returns
231    ///
232    /// A new [`ServiceBusError::AzureApiError`] instance
233    pub fn azure_api_error(
234        operation: impl Into<String>,
235        code: impl Into<String>,
236        status_code: u16,
237        message: impl Into<String>,
238    ) -> Self {
239        Self::AzureApiError {
240            code: code.into(),
241            status_code,
242            message: message.into(),
243            request_id: None,
244            operation: operation.into(),
245        }
246    }
247
248    /// Creates an Azure API error with request ID for tracing.
249    ///
250    /// # Arguments
251    ///
252    /// * `operation` - The operation that failed
253    /// * `code` - Azure error code
254    /// * `status_code` - HTTP status code
255    /// * `message` - Error message
256    /// * `request_id` - Azure request ID for support tracking
257    ///
258    /// # Returns
259    ///
260    /// A new [`ServiceBusError::AzureApiError`] with request ID
261    pub fn azure_api_error_with_request_id(
262        operation: impl Into<String>,
263        code: impl Into<String>,
264        status_code: u16,
265        message: impl Into<String>,
266        request_id: impl Into<String>,
267    ) -> Self {
268        Self::AzureApiError {
269            code: code.into(),
270            status_code,
271            message: message.into(),
272            request_id: Some(request_id.into()),
273            operation: operation.into(),
274        }
275    }
276
277    /// Extracts Azure error details from a reqwest Response.
278    ///
279    /// Parses Azure API error responses and extracts structured error information
280    /// including request IDs for tracking. Handles both JSON and plain text responses.
281    ///
282    /// # Arguments
283    ///
284    /// * `response` - The HTTP response from Azure API
285    /// * `operation` - The operation that resulted in this response
286    ///
287    /// # Returns
288    ///
289    /// A [`ServiceBusError::AzureApiError`] with extracted details
290    pub async fn from_azure_response(
291        response: reqwest::Response,
292        operation: impl Into<String>,
293    ) -> Self {
294        let operation = operation.into();
295        let status_code = response.status().as_u16();
296        let request_id = response
297            .headers()
298            .get("x-ms-request-id")
299            .and_then(|v| v.to_str().ok())
300            .map(|s| s.to_string());
301
302        // Try to extract Azure error details from response body
303        match response.text().await {
304            Ok(body) => {
305                // Try to parse Azure error response format
306                if let Ok(azure_error) = serde_json::from_str::<AzureErrorResponse>(&body) {
307                    Self::AzureApiError {
308                        code: azure_error.error.code,
309                        status_code,
310                        message: azure_error.error.message,
311                        request_id,
312                        operation,
313                    }
314                } else {
315                    // Fallback for non-JSON responses
316                    Self::AzureApiError {
317                        code: format!("HTTP_{status_code}"),
318                        status_code,
319                        message: if body.is_empty() {
320                            format!("HTTP {status_code} error")
321                        } else {
322                            body
323                        },
324                        request_id,
325                        operation,
326                    }
327                }
328            }
329            Err(_) => Self::AzureApiError {
330                code: format!("HTTP_{status_code}"),
331                status_code,
332                message: format!("HTTP {status_code} error - unable to read response body"),
333                request_id,
334                operation,
335            },
336        }
337    }
338
339    /// Checks if this is an Azure API error.
340    ///
341    /// # Returns
342    ///
343    /// `true` if this is an [`AzureApiError`], `false` otherwise
344    pub fn is_azure_api_error(&self) -> bool {
345        matches!(self, ServiceBusError::AzureApiError { .. })
346    }
347
348    /// Gets the Azure error code if this is an Azure API error.
349    ///
350    /// # Returns
351    ///
352    /// The Azure error code as a string slice, or `None` if not an Azure API error
353    pub fn azure_error_code(&self) -> Option<&str> {
354        match self {
355            ServiceBusError::AzureApiError { code, .. } => Some(code),
356            _ => None,
357        }
358    }
359
360    /// Gets the Azure request ID if available.
361    ///
362    /// Request IDs are useful for tracking issues with Azure support.
363    ///
364    /// # Returns
365    ///
366    /// The Azure request ID as a string slice, or `None` if not available
367    pub fn azure_request_id(&self) -> Option<&str> {
368        match self {
369            ServiceBusError::AzureApiError { request_id, .. } => request_id.as_deref(),
370            _ => None,
371        }
372    }
373}
374
375/// Azure API error response format
376#[derive(Debug, serde::Deserialize)]
377struct AzureErrorResponse {
378    error: AzureErrorDetails,
379}
380
381#[derive(Debug, serde::Deserialize)]
382struct AzureErrorDetails {
383    code: String,
384    message: String,
385}
386
387impl From<azure_core::Error> for ServiceBusError {
388    fn from(err: azure_core::Error) -> Self {
389        ServiceBusError::InternalError(err.to_string())
390    }
391}
392
393impl From<Box<dyn std::error::Error + Send + Sync>> for ServiceBusError {
394    fn from(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
395        ServiceBusError::InternalError(err.to_string())
396    }
397}
398
399impl From<tokio::time::error::Elapsed> for ServiceBusError {
400    fn from(err: tokio::time::error::Elapsed) -> Self {
401        ServiceBusError::OperationTimeout(err.to_string())
402    }
403}
404
405impl From<HttpError> for ServiceBusError {
406    fn from(err: HttpError) -> Self {
407        match err {
408            HttpError::ClientCreation { reason } => ServiceBusError::ConfigurationError(format!(
409                "HTTP client creation failed: {reason}"
410            )),
411            HttpError::RequestFailed { url, reason } => {
412                ServiceBusError::InternalError(format!("Request to {url} failed: {reason}"))
413            }
414            HttpError::Timeout { url, seconds } => ServiceBusError::OperationTimeout(format!(
415                "Request to {url} timed out after {seconds}s"
416            )),
417            HttpError::RateLimited {
418                retry_after_seconds,
419            } => ServiceBusError::InternalError(format!(
420                "Rate limited, retry after {retry_after_seconds}s"
421            )),
422            HttpError::InvalidResponse { expected, actual } => ServiceBusError::ConfigurationError(
423                format!("Invalid response: expected {expected}, got {actual}"),
424            ),
425        }
426    }
427}
428
429impl From<CacheError> for ServiceBusError {
430    fn from(err: CacheError) -> Self {
431        match err {
432            CacheError::Expired { key } => {
433                ServiceBusError::InternalError(format!("Cache entry expired: {key}"))
434            }
435            CacheError::Miss { key } => {
436                ServiceBusError::InternalError(format!("Cache miss: {key}"))
437            }
438            CacheError::Full { key } => {
439                ServiceBusError::InternalError(format!("Cache full, cannot add: {key}"))
440            }
441            CacheError::OperationFailed { reason } => {
442                ServiceBusError::InternalError(format!("Cache operation failed: {reason}"))
443            }
444        }
445    }
446}
447
448/// Type alias for [`Result`] with [`ServiceBusError`] as the error type.
449///
450/// Provides convenient result handling for all Service Bus operations.
451///
452/// # Examples
453///
454/// ```no_run
455/// use quetty_server::service_bus_manager::{ServiceBusResult, ServiceBusError};
456///
457/// fn get_queue_info() -> ServiceBusResult<String> {
458///     // ... operation that might fail
459///     Ok("queue-info".to_string())
460/// }
461/// ```
462pub type ServiceBusResult<T> = Result<T, ServiceBusError>;