docker_image_pusher/error/
handlers.rs

1//! Standardized error handling patterns
2
3use crate::error::{RegistryError, Result};
4use reqwest::StatusCode;
5use std::time::Duration;
6
7/// HTTP error handler for registry operations
8pub struct HttpErrorHandler;
9
10impl HttpErrorHandler {
11    /// Handle upload-related HTTP errors with standardized messages
12    pub fn handle_upload_error(
13        status: StatusCode,
14        error_text: &str,
15        context: &str,
16    ) -> RegistryError {
17        let error_msg = match status.as_u16() {
18            400 => {
19                if error_text.contains("exist blob require digest") {
20                    format!(
21                        "Digest validation failed - Registry reports blob exists but digest mismatch: {}",
22                        error_text
23                    )
24                } else if error_text.contains("BAD_REQUEST") {
25                    format!(
26                        "Bad request - Check digest format and data integrity: {}",
27                        error_text
28                    )
29                } else {
30                    format!("Bad request during {}: {}", context, error_text)
31                }
32            }
33            401 => format!("Authentication failed during {}: {}", context, error_text),
34            403 => format!("Permission denied for {}: {}", context, error_text),
35            404 => format!(
36                "Repository not found or {} session expired: {}",
37                context, error_text
38            ),
39            409 => format!(
40                "Conflict - Blob already exists with different digest: {}",
41                error_text
42            ),
43            413 => format!("File too large for {}: {}", context, error_text),
44            422 => format!("Invalid digest or data for {}: {}", context, error_text),
45            500 => format!("Registry server error during {}: {}", context, error_text),
46            502 | 503 => format!("Registry unavailable during {}: {}", context, error_text),
47            507 => format!("Registry out of storage during {}: {}", context, error_text),
48            508 => format!("Streaming {} timeout: {}", context, error_text),
49            _ => format!("{} failed (status {}): {}", context, status, error_text),
50        };
51
52        RegistryError::Upload(error_msg)
53    }
54
55    /// Handle authentication-related HTTP errors
56    pub fn handle_auth_error(status: StatusCode, error_text: &str) -> RegistryError {
57        let error_msg = match status.as_u16() {
58            400 => "Invalid token request parameters".to_string(),
59            401 => "Invalid credentials provided".to_string(),
60            403 => "Access denied - insufficient permissions".to_string(),
61            404 => "Authentication endpoint not found".to_string(),
62            _ => format!("Authentication failed (status {}): {}", status, error_text),
63        };
64
65        RegistryError::Auth(error_msg)
66    }
67
68    /// Handle registry-related HTTP errors
69    pub fn handle_registry_error(
70        status: StatusCode,
71        error_text: &str,
72        operation: &str,
73    ) -> RegistryError {
74        let error_msg = match status.as_u16() {
75            401 => format!(
76                "Unauthorized to perform {} operation: {}",
77                operation, error_text
78            ),
79            403 => format!(
80                "Forbidden: insufficient permissions for {}: {}",
81                operation, error_text
82            ),
83            404 => format!("Resource not found for {}: {}", operation, error_text),
84            429 => format!("Rate limited during {}: {}", operation, error_text),
85            500 => format!("Registry server error during {}: {}", operation, error_text),
86            502 | 503 => format!("Registry unavailable for {}: {}", operation, error_text),
87            _ => format!("{} failed (status {}): {}", operation, status, error_text),
88        };
89
90        RegistryError::Registry(error_msg)
91    }
92
93    /// Handle streaming upload specific errors
94    pub fn handle_streaming_error(status: StatusCode, error_text: &str) -> RegistryError {
95        let error_msg = match status.as_u16() {
96            400 => {
97                if error_text.contains("DIGEST_INVALID") {
98                    "Digest validation failed - Registry reports uploaded content doesn't match expected digest".to_string()
99                } else {
100                    format!("Bad request during streaming upload: {}", error_text)
101                }
102            }
103            413 => "File too large for registry".to_string(),
104            507 => "Insufficient storage space on registry".to_string(),
105            401 => "Authentication failed during upload".to_string(),
106            403 => "Permission denied for upload".to_string(),
107            408 | 504 => "Streaming upload timeout".to_string(),
108            500 => {
109                if error_text.contains("s3aws") || error_text.contains("DriverName") {
110                    format!(
111                        "Registry storage backend error (S3): {}. This is typically a temporary issue with the registry's storage system",
112                        error_text
113                    )
114                } else {
115                    format!("Registry internal server error: {}", error_text)
116                }
117            }
118            502 | 503 => format!("Registry temporarily unavailable: {}", error_text),
119            _ => format!(
120                "Streaming upload failed (status {}): {}",
121                status, error_text
122            ),
123        };
124
125        RegistryError::Upload(error_msg)
126    }
127
128    /// Check if error indicates a storage backend issue that might be temporary
129    pub fn is_storage_backend_error(error_text: &str) -> bool {
130        error_text.contains("s3aws")
131            || error_text.contains("DriverName")
132            || error_text.contains("storage backend")
133            || error_text.contains("500 Internal Server Error")
134    }
135
136    /// Get suggested retry delay for storage backend errors (exponential backoff)
137    pub fn get_storage_error_retry_delay(attempt: u32, base_delay_secs: u64) -> Duration {
138        let backoff_multiplier = 2_u64.pow(attempt.min(4));
139        Duration::from_secs(base_delay_secs * backoff_multiplier)
140    }
141}
142
143/// Network error categorization and handling
144pub struct NetworkErrorHandler;
145
146impl NetworkErrorHandler {
147    /// Categorize and format network errors with helpful context
148    pub fn handle_network_error(error: &reqwest::Error, context: &str) -> RegistryError {
149        if error.is_timeout() {
150            RegistryError::Network(format!("Timeout during {}", context))
151        } else if error.is_connect() {
152            RegistryError::Network(format!("Connection failed during {}", context))
153        } else {
154            RegistryError::Network(format!("Network error during {}: {}", context, error))
155        }
156    }
157}
158
159/// Validation error utilities
160pub struct ValidationErrorHandler;
161
162impl ValidationErrorHandler {
163    /// Validate required fields
164    pub fn validate_required_field(field_name: &str, value: &Option<String>) -> Result<()> {
165        if value.is_none() || value.as_ref().unwrap().is_empty() {
166            Err(RegistryError::Validation(format!(
167                "{} is required",
168                field_name
169            )))
170        } else {
171            Ok(())
172        }
173    }
174}
175
176/// Macro for standardizing error context
177#[macro_export]
178macro_rules! with_context {
179    ($result:expr, $context:expr) => {
180        $result.map_err(|e| match e {
181            RegistryError::Auth(msg) => RegistryError::Auth(format!("{}: {}", $context, msg)),
182            RegistryError::Network(msg) => RegistryError::Network(format!("{}: {}", $context, msg)),
183            RegistryError::Upload(msg) => RegistryError::Upload(format!("{}: {}", $context, msg)),
184            RegistryError::Io(msg) => RegistryError::Io(format!("{}: {}", $context, msg)),
185            RegistryError::Parse(msg) => RegistryError::Parse(format!("{}: {}", $context, msg)),
186            RegistryError::Registry(msg) => {
187                RegistryError::Registry(format!("{}: {}", $context, msg))
188            }
189            RegistryError::ImageParsing(msg) => {
190                RegistryError::ImageParsing(format!("{}: {}", $context, msg))
191            }
192            RegistryError::Validation(msg) => {
193                RegistryError::Validation(format!("{}: {}", $context, msg))
194            }
195            RegistryError::Cache { message, path } => RegistryError::Cache {
196                message: format!("{}: {}", $context, message),
197                path,
198            },
199            RegistryError::NotFound(msg) => {
200                RegistryError::NotFound(format!("{}: {}", $context, msg))
201            }
202        })
203    };
204}