docker_image_pusher/error/
handlers.rs1use crate::error::{Result, PusherError};
4use reqwest::StatusCode;
5
6pub struct HttpErrorHandler;
8
9impl HttpErrorHandler {
10 pub fn handle_upload_error(status: StatusCode, error_text: &str, context: &str) -> PusherError {
12 let error_msg = match status.as_u16() {
13 400 => {
14 if error_text.contains("exist blob require digest") {
15 format!("Digest validation failed - Registry reports blob exists but digest mismatch: {}", error_text)
16 } else if error_text.contains("BAD_REQUEST") {
17 format!("Bad request - Check digest format and data integrity: {}", error_text)
18 } else {
19 format!("Bad request during {}: {}", context, error_text)
20 }
21 },
22 401 => format!("Authentication failed during {}: {}", context, error_text),
23 403 => format!("Permission denied for {}: {}", context, error_text),
24 404 => format!("Repository not found or {} session expired: {}", context, error_text),
25 409 => format!("Conflict - Blob already exists with different digest: {}", error_text),
26 413 => format!("File too large for {}: {}", context, error_text),
27 422 => format!("Invalid digest or data for {}: {}", context, error_text),
28 500 => format!("Registry server error during {}: {}", context, error_text),
29 502 | 503 => format!("Registry unavailable during {}: {}", context, error_text),
30 507 => format!("Registry out of storage during {}: {}", context, error_text),
31 508 => format!("Streaming {} timeout: {}", context, error_text),
32 _ => format!("{} failed (status {}): {}", context, status, error_text)
33 };
34
35 PusherError::Upload(error_msg)
36 }
37
38 pub fn handle_auth_error(status: StatusCode, error_text: &str) -> PusherError {
40 let error_msg = match status.as_u16() {
41 400 => "Invalid token request parameters".to_string(),
42 401 => "Invalid credentials provided".to_string(),
43 403 => "Access denied - insufficient permissions".to_string(),
44 404 => "Authentication endpoint not found".to_string(),
45 _ => format!("Authentication failed (status {}): {}", status, error_text)
46 };
47
48 PusherError::Authentication(error_msg)
49 }
50
51 pub fn handle_registry_error(status: StatusCode, error_text: &str, operation: &str) -> PusherError {
53 let error_msg = match status.as_u16() {
54 401 => format!("Unauthorized to perform {} operation: {}", operation, error_text),
55 403 => format!("Forbidden: insufficient permissions for {}: {}", operation, error_text),
56 404 => format!("Resource not found for {}: {}", operation, error_text),
57 429 => format!("Rate limited during {}: {}", operation, error_text),
58 500 => format!("Registry server error during {}: {}", operation, error_text),
59 502 | 503 => format!("Registry unavailable for {}: {}", operation, error_text),
60 _ => format!("{} failed (status {}): {}", operation, status, error_text)
61 };
62
63 PusherError::Registry(error_msg)
64 }
65
66 pub fn handle_streaming_error(status: StatusCode, error_text: &str) -> PusherError {
68 let error_msg = match status.as_u16() {
69 413 => "File too large for registry".to_string(),
70 507 => "Insufficient storage space on registry".to_string(),
71 401 => "Authentication failed during upload".to_string(),
72 403 => "Permission denied for upload".to_string(),
73 408 | 504 => "Streaming upload timeout".to_string(),
74 _ => format!("Streaming upload failed (status {}): {}", status, error_text)
75 };
76
77 PusherError::Upload(error_msg)
78 }
79}
80
81pub struct NetworkErrorHandler;
83
84impl NetworkErrorHandler {
85 pub fn handle_network_error(error: &reqwest::Error, context: &str) -> PusherError {
87 if error.is_timeout() {
88 PusherError::Timeout(format!("{} timeout: {}", context, error))
89 } else if error.is_connect() {
90 PusherError::Network(format!("Connection error during {}: {}", context, error))
91 } else if error.to_string().contains("dns") {
92 PusherError::Network(format!("DNS resolution error for {}: {}", context, error))
93 } else if error.to_string().contains("certificate") {
94 PusherError::Network(format!("TLS certificate error during {}: {}", context, error))
95 } else {
96 PusherError::Network(format!("{} network error: {}", context, error))
97 }
98 }
99}
100
101pub struct ValidationErrorHandler;
103
104impl ValidationErrorHandler {
105 pub fn validate_file_path(file_path: &str) -> Result<()> {
107 use std::path::Path;
108
109 let path = Path::new(file_path);
110
111 if !path.exists() {
112 return Err(PusherError::Validation(format!(
113 "Input file does not exist: {}", file_path
114 )));
115 }
116
117 if !path.is_file() {
118 return Err(PusherError::Validation(format!(
119 "Input path is not a file: {}", file_path
120 )));
121 }
122
123 let extension = path.extension()
125 .and_then(|ext| ext.to_str())
126 .unwrap_or("");
127
128 if !matches!(extension.to_lowercase().as_str(), "tar" | "tar.gz" | "tgz") {
129 return Err(PusherError::Validation(format!(
130 "Input file must be a tar archive (.tar, .tar.gz, or .tgz): {}", file_path
131 )));
132 }
133
134 Ok(())
135 }
136
137 pub fn validate_repository_url(url: &str) -> Result<()> {
139 if url.is_empty() {
140 return Err(PusherError::Validation(
141 "Repository URL cannot be empty".to_string()
142 ));
143 }
144
145 if !url.contains("://") {
146 return Err(PusherError::Validation(
147 "Repository URL must include protocol (http:// or https://)".to_string()
148 ));
149 }
150
151 Ok(())
152 }
153
154 pub fn validate_credentials(username: &Option<String>, password: &Option<String>) -> Result<()> {
156 match (username, password) {
157 (Some(_), None) => {
158 Err(PusherError::Validation(
159 "Password is required when username is provided".to_string()
160 ))
161 },
162 (None, Some(_)) => {
163 Err(PusherError::Validation(
164 "Username is required when password is provided".to_string()
165 ))
166 },
167 _ => Ok(()) }
169 }
170
171 pub fn validate_timeout(timeout: u64) -> Result<()> {
173 if timeout == 0 {
174 return Err(PusherError::Validation(
175 "Timeout must be greater than 0".to_string()
176 ));
177 }
178
179 if timeout > 86400 { return Err(PusherError::Validation(
181 "Timeout cannot exceed 24 hours (86400 seconds)".to_string()
182 ));
183 }
184
185 Ok(())
186 }
187}
188
189#[macro_export]
191macro_rules! with_context {
192 ($result:expr, $context:expr) => {
193 $result.map_err(|e| match e {
194 PusherError::Config(msg) => PusherError::Config(format!("{}: {}", $context, msg)),
195 PusherError::Authentication(msg) => PusherError::Authentication(format!("{}: {}", $context, msg)),
196 PusherError::Network(msg) => PusherError::Network(format!("{}: {}", $context, msg)),
197 PusherError::Upload(msg) => PusherError::Upload(format!("{}: {}", $context, msg)),
198 PusherError::Io(msg) => PusherError::Io(format!("{}: {}", $context, msg)),
199 PusherError::Parse(msg) => PusherError::Parse(format!("{}: {}", $context, msg)),
200 PusherError::Registry(msg) => PusherError::Registry(format!("{}: {}", $context, msg)),
201 PusherError::ImageParsing(msg) => PusherError::ImageParsing(format!("{}: {}", $context, msg)),
202 PusherError::Validation(msg) => PusherError::Validation(format!("{}: {}", $context, msg)),
203 PusherError::Timeout(msg) => PusherError::Timeout(format!("{}: {}", $context, msg)),
204 })
205 };
206}