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>;