1use std::fmt;
5use thiserror::Error;
6
7#[derive(Error, Debug)]
9pub enum TurboCdnError {
10 #[error("Network error: {0}")]
12 Network(#[from] reqwest::Error),
13
14 #[error("IO error: {0}")]
16 Io(#[from] std::io::Error),
17
18 #[error("Invalid URL: {0}")]
20 InvalidUrl(#[from] url::ParseError),
21
22 #[error("JSON parsing error: {0}")]
24 Json(#[from] serde_json::Error),
25
26 #[error("Configuration error: {message}")]
28 Config { message: String },
29
30 #[error("Download failed: {message}")]
32 Download { message: String },
33
34 #[error("Source validation failed: {message}")]
36 SourceValidation { message: String },
37
38 #[error("Compliance check failed: {message}")]
40 Compliance { message: String },
41
42 #[error("Cache error: {message}")]
44 Cache { message: String },
45
46 #[error("Routing error: {message}")]
48 Routing { message: String },
49
50 #[error("Authentication failed: {message}")]
52 Authentication { message: String },
53
54 #[error("Rate limit exceeded: {message}")]
56 RateLimit { message: String },
57
58 #[error("Operation timed out: {message}")]
60 Timeout { message: String },
61
62 #[error("Checksum validation failed: expected {expected}, got {actual}")]
64 ChecksumMismatch { expected: String, actual: String },
65
66 #[error("File not found: {path}")]
68 FileNotFound { path: String },
69
70 #[error("HTTP {status_code}: {message}")]
72 HttpStatus {
73 status_code: u16,
74 message: String,
75 url: String,
76 },
77
78 #[error("Server error {status_code}: {message}")]
80 ServerError {
81 status_code: u16,
82 message: String,
83 url: String,
84 },
85
86 #[error("Unsupported operation: {message}")]
88 Unsupported { message: String },
89
90 #[error("Internal error: {message}")]
92 Internal { message: String },
93}
94
95impl TurboCdnError {
96 pub fn config<S: Into<String>>(message: S) -> Self {
98 Self::Config {
99 message: message.into(),
100 }
101 }
102
103 pub fn download<S: Into<String>>(message: S) -> Self {
105 Self::Download {
106 message: message.into(),
107 }
108 }
109
110 pub fn source_validation<S: Into<String>>(message: S) -> Self {
112 Self::SourceValidation {
113 message: message.into(),
114 }
115 }
116
117 pub fn compliance<S: Into<String>>(message: S) -> Self {
119 Self::Compliance {
120 message: message.into(),
121 }
122 }
123
124 pub fn cache<S: Into<String>>(message: S) -> Self {
126 Self::Cache {
127 message: message.into(),
128 }
129 }
130
131 pub fn routing<S: Into<String>>(message: S) -> Self {
133 Self::Routing {
134 message: message.into(),
135 }
136 }
137
138 pub fn authentication<S: Into<String>>(message: S) -> Self {
140 Self::Authentication {
141 message: message.into(),
142 }
143 }
144
145 pub fn rate_limit<S: Into<String>>(message: S) -> Self {
147 Self::RateLimit {
148 message: message.into(),
149 }
150 }
151
152 pub fn timeout<S: Into<String>>(message: S) -> Self {
154 Self::Timeout {
155 message: message.into(),
156 }
157 }
158
159 pub fn checksum_mismatch<S: Into<String>>(expected: S, actual: S) -> Self {
161 Self::ChecksumMismatch {
162 expected: expected.into(),
163 actual: actual.into(),
164 }
165 }
166
167 pub fn file_not_found<S: Into<String>>(path: S) -> Self {
169 Self::FileNotFound { path: path.into() }
170 }
171
172 pub fn http_status<S: Into<String>>(status_code: u16, message: S, url: S) -> Self {
174 Self::HttpStatus {
175 status_code,
176 message: message.into(),
177 url: url.into(),
178 }
179 }
180
181 pub fn server_error<S: Into<String>>(status_code: u16, message: S, url: S) -> Self {
183 Self::ServerError {
184 status_code,
185 message: message.into(),
186 url: url.into(),
187 }
188 }
189
190 pub fn from_status_code<S: Into<String>>(status_code: u16, url: S) -> Self {
192 let url_str = url.into();
193 let message = match status_code {
194 400 => "Bad Request",
195 401 => "Unauthorized",
196 403 => "Forbidden",
197 404 => "Not Found",
198 405 => "Method Not Allowed",
199 408 => "Request Timeout",
200 429 => "Too Many Requests",
201 500 => "Internal Server Error",
202 502 => "Bad Gateway",
203 503 => "Service Unavailable",
204 504 => "Gateway Timeout",
205 _ => "Unknown Error",
206 };
207
208 if status_code >= 500 {
209 Self::ServerError {
210 status_code,
211 message: message.to_string(),
212 url: url_str,
213 }
214 } else if status_code == 429 {
215 Self::RateLimit {
216 message: format!("Rate limited by {url_str}"),
217 }
218 } else {
219 Self::HttpStatus {
220 status_code,
221 message: message.to_string(),
222 url: url_str,
223 }
224 }
225 }
226
227 pub fn unsupported<S: Into<String>>(message: S) -> Self {
229 Self::Unsupported {
230 message: message.into(),
231 }
232 }
233
234 pub fn internal<S: Into<String>>(message: S) -> Self {
236 Self::Internal {
237 message: message.into(),
238 }
239 }
240
241 pub fn network<S: Into<String>>(message: S) -> Self {
243 let msg = message.into();
244 Self::Internal {
245 message: format!("Network error: {msg}"),
246 }
247 }
248
249 pub fn io<S: Into<String>>(message: S) -> Self {
251 let msg = message.into();
252 Self::Internal {
253 message: format!("IO error: {msg}"),
254 }
255 }
256
257 pub fn is_retryable(&self) -> bool {
259 match self {
260 TurboCdnError::Network(_) => true,
262 TurboCdnError::Timeout { .. } => true,
264 TurboCdnError::RateLimit { .. } => true,
266 TurboCdnError::Io(_) => true,
268 TurboCdnError::ServerError { .. } => true,
270 TurboCdnError::HttpStatus { .. } => false,
272 _ => false,
274 }
275 }
276
277 pub fn should_try_next_mirror(&self) -> bool {
280 match self {
281 TurboCdnError::HttpStatus { status_code, .. } => *status_code == 404,
283 TurboCdnError::ServerError { .. } => true,
285 TurboCdnError::Network(_) => true,
287 TurboCdnError::Timeout { .. } => true,
289 _ => false,
290 }
291 }
292
293 pub fn status_code(&self) -> Option<u16> {
295 match self {
296 TurboCdnError::HttpStatus { status_code, .. } => Some(*status_code),
297 TurboCdnError::ServerError { status_code, .. } => Some(*status_code),
298 _ => None,
299 }
300 }
301
302 pub fn category(&self) -> &'static str {
304 match self {
305 TurboCdnError::Network(_) => "network",
306 TurboCdnError::Io(_) => "io",
307 TurboCdnError::InvalidUrl(_) => "url",
308 TurboCdnError::Json(_) => "json",
309 TurboCdnError::Config { .. } => "config",
310 TurboCdnError::Download { .. } => "download",
311 TurboCdnError::SourceValidation { .. } => "source_validation",
312 TurboCdnError::Compliance { .. } => "compliance",
313 TurboCdnError::Cache { .. } => "cache",
314 TurboCdnError::Routing { .. } => "routing",
315 TurboCdnError::Authentication { .. } => "authentication",
316 TurboCdnError::RateLimit { .. } => "rate_limit",
317 TurboCdnError::Timeout { .. } => "timeout",
318 TurboCdnError::ChecksumMismatch { .. } => "checksum",
319 TurboCdnError::FileNotFound { .. } => "file_not_found",
320 TurboCdnError::HttpStatus { .. } => "http_status",
321 TurboCdnError::ServerError { .. } => "server_error",
322 TurboCdnError::Unsupported { .. } => "unsupported",
323 TurboCdnError::Internal { .. } => "internal",
324 }
325 }
326}
327
328pub type Result<T> = std::result::Result<T, TurboCdnError>;
330
331#[derive(Debug, Clone)]
333pub struct ErrorContext {
334 pub operation: String,
335 pub source: Option<String>,
336 pub file_path: Option<String>,
337 pub url: Option<String>,
338 pub timestamp: chrono::DateTime<chrono::Utc>,
339}
340
341impl ErrorContext {
342 pub fn new(operation: impl Into<String>) -> Self {
343 Self {
344 operation: operation.into(),
345 source: None,
346 file_path: None,
347 url: None,
348 timestamp: chrono::Utc::now(),
349 }
350 }
351
352 pub fn with_source(mut self, source: impl Into<String>) -> Self {
353 self.source = Some(source.into());
354 self
355 }
356
357 pub fn with_file_path(mut self, path: impl Into<String>) -> Self {
358 self.file_path = Some(path.into());
359 self
360 }
361
362 pub fn with_url(mut self, url: impl Into<String>) -> Self {
363 self.url = Some(url.into());
364 self
365 }
366}
367
368impl fmt::Display for ErrorContext {
369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370 let operation = &self.operation;
371 write!(f, "Operation: {operation}")?;
372 if let Some(source) = &self.source {
373 write!(f, ", Source: {source}")?;
374 }
375 if let Some(path) = &self.file_path {
376 write!(f, ", File: {path}")?;
377 }
378 if let Some(url) = &self.url {
379 write!(f, ", URL: {url}")?;
380 }
381 let timestamp = self.timestamp.format("%Y-%m-%d %H:%M:%S UTC");
382 write!(f, ", Time: {timestamp}")
383 }
384}