prism3_retry/error.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025.
4 * 3-Prism Co. Ltd.
5 *
6 * All rights reserved.
7 *
8 ******************************************************************************/
9//! # Retry Error Types
10//!
11//! Defines error types used in the retry module.
12//!
13//! # Author
14//!
15//! Haixing Hu
16
17use std::error::Error;
18use std::fmt;
19
20/// Error type for the retry module
21///
22/// Defines various error conditions that can occur during retry operations, including exceeding maximum retries,
23/// exceeding duration limits, operation abortion, configuration errors, etc.
24///
25/// # Features
26///
27/// - Supports unified handling of multiple error types
28/// - Provides detailed error information and context
29/// - Supports error chain tracking (via the source method)
30/// - Implements the standard Error trait for interoperability with other error types
31///
32/// # Use Cases
33///
34/// Suitable for operations requiring retry mechanisms, such as network requests, file operations, database connections, etc.
35/// Returns a corresponding RetryError when retry strategies fail or encounter unrecoverable errors.
36///
37/// # Example
38///
39/// ```rust
40/// use prism3_retry::RetryError;
41///
42/// // Create maximum attempts exceeded error
43/// let error = RetryError::max_attempts_exceeded(5, 3);
44/// println!("Error: {}", error);
45///
46/// // Create execution error
47/// let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
48/// let retry_error = RetryError::execution_error(io_error);
49/// println!("Execution error: {}", retry_error);
50/// ```
51///
52/// # Author
53///
54/// Haixing Hu
55///
56#[derive(Debug)]
57pub enum RetryError {
58 /// Maximum attempts exceeded
59 ///
60 /// Triggered when the number of retries reaches or exceeds the preset maximum retry count.
61 ///
62 /// # Fields
63 ///
64 /// * `attempts` - Actual number of attempts
65 /// * `max_attempts` - Maximum allowed retry count
66 MaxAttemptsExceeded { attempts: u32, max_attempts: u32 },
67
68 /// Maximum duration exceeded
69 ///
70 /// Triggered when the total duration of retry operations exceeds the preset maximum duration.
71 ///
72 /// # Fields
73 ///
74 /// * `duration` - Actual time consumed
75 /// * `max_duration` - Maximum allowed duration
76 ///
77 MaxDurationExceeded {
78 duration: std::time::Duration,
79 max_duration: std::time::Duration,
80 },
81
82 /// Single operation timeout
83 ///
84 /// Triggered when the execution time of a single operation exceeds the configured operation timeout.
85 /// This differs from MaxDurationExceeded, which is for the total time limit of the entire retry process.
86 ///
87 /// # Fields
88 ///
89 /// * `duration` - Actual execution time
90 /// * `timeout` - Configured timeout duration
91 ///
92 OperationTimeout {
93 duration: std::time::Duration,
94 timeout: std::time::Duration,
95 },
96
97 /// Operation aborted
98 ///
99 /// Triggered when retry operation is aborted by external factors (e.g., user cancellation, system signals).
100 ///
101 /// # Fields
102 ///
103 /// * `reason` - Description of the abort reason
104 ///
105 Aborted { reason: String },
106
107 /// Configuration error
108 ///
109 /// Triggered when retry configuration parameters are invalid or conflicting.
110 ///
111 /// # Fields
112 ///
113 /// * `message` - Error description message
114 ///
115 ConfigError { message: String },
116
117 /// Delay strategy error
118 ///
119 /// Triggered when delay strategy configuration is erroneous or an error occurs while calculating delays.
120 ///
121 /// # Fields
122 ///
123 /// * `message` - Error description message
124 ///
125 DelayStrategyError { message: String },
126
127 /// Execution error
128 ///
129 /// Wraps the original error when the retried operation itself fails.
130 ///
131 /// # Fields
132 ///
133 /// * `source` - Original error, supports error chain tracking
134 ///
135 ExecutionError {
136 source: Box<dyn Error + Send + Sync>,
137 },
138
139 /// Other errors
140 ///
141 /// Used to represent other error situations that don't fall into the above categories.
142 ///
143 /// # Fields
144 ///
145 /// * `message` - Error description message
146 ///
147 Other { message: String },
148}
149
150impl fmt::Display for RetryError {
151 /// Format error information into a readable string
152 ///
153 /// Provides error descriptions for each error type, including relevant contextual information.
154 ///
155 /// # Parameters
156 ///
157 /// * `f` - Formatter
158 ///
159 /// # Returns
160 ///
161 /// Returns formatting result
162 ///
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 match self {
165 RetryError::MaxAttemptsExceeded {
166 attempts,
167 max_attempts,
168 } => {
169 write!(
170 f,
171 "Maximum attempts exceeded: {} (max: {})",
172 attempts, max_attempts
173 )
174 }
175 RetryError::MaxDurationExceeded {
176 duration,
177 max_duration,
178 } => {
179 write!(
180 f,
181 "Maximum duration exceeded: {:?} (max: {:?})",
182 duration, max_duration
183 )
184 }
185 RetryError::OperationTimeout { duration, timeout } => {
186 write!(
187 f,
188 "Operation timeout: execution time {:?} exceeded configured timeout {:?}",
189 duration, timeout
190 )
191 }
192 RetryError::Aborted { reason } => {
193 write!(f, "Operation aborted: {}", reason)
194 }
195 RetryError::ConfigError { message } => {
196 write!(f, "Configuration error: {}", message)
197 }
198 RetryError::DelayStrategyError { message } => {
199 write!(f, "Delay strategy error: {}", message)
200 }
201 RetryError::ExecutionError { source } => {
202 write!(f, "Execution error: {}", source)
203 }
204 RetryError::Other { message } => {
205 write!(f, "Other error: {}", message)
206 }
207 }
208 }
209}
210
211impl Error for RetryError {
212 /// Get the root cause of the error
213 ///
214 /// For ExecutionError type, returns the original error; for other types, returns None.
215 /// This supports error chain tracking, aiding in debugging and error handling.
216 ///
217 /// # Returns
218 ///
219 /// Returns the root cause of the error, or None if it doesn't exist
220 fn source(&self) -> Option<&(dyn Error + 'static)> {
221 match self {
222 RetryError::ExecutionError { source } => Some(source.as_ref()),
223 _ => None,
224 }
225 }
226}
227
228impl RetryError {
229 /// Create maximum attempts exceeded error
230 ///
231 /// Use this method to create an error when the retry count reaches or exceeds the preset maximum retry count.
232 ///
233 /// # Parameters
234 ///
235 /// * `attempts` - Actual number of attempts
236 /// * `max_attempts` - Maximum allowed retry count
237 ///
238 /// # Returns
239 ///
240 /// Returns a RetryError containing retry count information
241 ///
242 /// # Example
243 ///
244 /// ```rust
245 /// use prism3_retry::RetryError;
246 ///
247 /// let error = RetryError::max_attempts_exceeded(5, 3);
248 /// assert!(error.to_string().contains("Maximum attempts exceeded"));
249 /// ```
250 pub fn max_attempts_exceeded(attempts: u32, max_attempts: u32) -> Self {
251 RetryError::MaxAttemptsExceeded {
252 attempts,
253 max_attempts,
254 }
255 }
256
257 /// Create maximum duration exceeded error
258 ///
259 /// Use this method to create an error when the total duration of retry operations exceeds the preset maximum duration.
260 ///
261 /// # Parameters
262 ///
263 /// * `duration` - Actual time consumed
264 /// * `max_duration` - Maximum allowed duration
265 ///
266 /// # Returns
267 ///
268 /// Returns a RetryError containing time information
269 ///
270 /// # Example
271 ///
272 /// ```rust
273 /// use prism3_retry::RetryError;
274 /// use std::time::Duration;
275 ///
276 /// let error = RetryError::max_duration_exceeded(
277 /// Duration::from_secs(10),
278 /// Duration::from_secs(5)
279 /// );
280 /// assert!(error.to_string().contains("Maximum duration exceeded"));
281 /// ```
282 pub fn max_duration_exceeded(
283 duration: std::time::Duration,
284 max_duration: std::time::Duration,
285 ) -> Self {
286 RetryError::MaxDurationExceeded {
287 duration,
288 max_duration,
289 }
290 }
291
292 /// Create single operation timeout error
293 ///
294 /// Use this method to create an error when the execution time of a single operation exceeds the configured operation timeout.
295 ///
296 /// # Parameters
297 ///
298 /// * `duration` - Actual execution time
299 /// * `timeout` - Configured timeout duration
300 ///
301 /// # Returns
302 ///
303 /// Returns a RetryError containing time information
304 ///
305 /// # Example
306 ///
307 /// ```rust
308 /// use prism3_retry::RetryError;
309 /// use std::time::Duration;
310 ///
311 /// let error = RetryError::operation_timeout(
312 /// Duration::from_secs(10),
313 /// Duration::from_secs(5)
314 /// );
315 /// assert!(error.to_string().contains("Operation timeout"));
316 /// ```
317 pub fn operation_timeout(duration: std::time::Duration, timeout: std::time::Duration) -> Self {
318 RetryError::OperationTimeout { duration, timeout }
319 }
320
321 /// Create abort error
322 ///
323 /// Use this method to create an error when the retry operation is aborted by external factors.
324 ///
325 /// # Parameters
326 ///
327 /// * `reason` - Reason for abortion
328 ///
329 /// # Returns
330 ///
331 /// Returns a RetryError containing the abort reason
332 ///
333 /// # Example
334 ///
335 /// ```rust
336 /// use prism3_retry::RetryError;
337 ///
338 /// let error = RetryError::aborted("User cancelled operation");
339 /// assert!(error.to_string().contains("Operation aborted"));
340 /// ```
341 pub fn aborted(reason: &str) -> Self {
342 RetryError::Aborted {
343 reason: reason.to_string(),
344 }
345 }
346
347 /// Create configuration error
348 ///
349 /// Use this method to create an error when retry configuration parameters are invalid or conflicting.
350 ///
351 /// # Parameters
352 ///
353 /// * `message` - Error description message
354 ///
355 /// # Returns
356 ///
357 /// Returns a RetryError containing error information
358 ///
359 /// # Example
360 ///
361 /// ```rust
362 /// use prism3_retry::RetryError;
363 ///
364 /// let error = RetryError::config_error("Maximum retry count cannot be negative");
365 /// assert!(error.to_string().contains("Configuration error"));
366 /// ```
367 pub fn config_error(message: &str) -> Self {
368 RetryError::ConfigError {
369 message: message.to_string(),
370 }
371 }
372
373 /// Create delay strategy error
374 ///
375 /// Use this method to create an error when delay strategy configuration is erroneous or an error occurs while calculating delays.
376 ///
377 /// # Parameters
378 ///
379 /// * `message` - Error description message
380 ///
381 /// # Returns
382 ///
383 /// Returns a RetryError containing error information
384 ///
385 /// # Example
386 ///
387 /// ```rust
388 /// use prism3_retry::RetryError;
389 ///
390 /// let error = RetryError::delay_strategy_error("Delay time calculation overflow");
391 /// assert!(error.to_string().contains("Delay strategy error"));
392 /// ```
393 pub fn delay_strategy_error(message: &str) -> Self {
394 RetryError::DelayStrategyError {
395 message: message.to_string(),
396 }
397 }
398
399 /// Create execution error
400 ///
401 /// Use this method to wrap the original error as RetryError when the retried operation itself fails.
402 ///
403 /// # Parameters
404 ///
405 /// * `error` - Original error, must implement Error + Send + Sync trait
406 ///
407 /// # Returns
408 ///
409 /// Returns a RetryError wrapping the original error
410 ///
411 /// # Example
412 ///
413 /// ```rust
414 /// use prism3_retry::RetryError;
415 ///
416 /// let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
417 /// let retry_error = RetryError::execution_error(io_error);
418 /// assert!(retry_error.to_string().contains("Execution error"));
419 /// ```
420 pub fn execution_error<E: Error + Send + Sync + 'static>(error: E) -> Self {
421 RetryError::ExecutionError {
422 source: Box::new(error),
423 }
424 }
425
426 /// Create execution error (from Box<dyn Error>)
427 ///
428 /// When you already have a Box<dyn Error>, use this method to create an execution error directly.
429 /// This avoids additional boxing operations.
430 ///
431 /// # Parameters
432 ///
433 /// * `error` - Boxed error
434 ///
435 /// # Returns
436 ///
437 /// Returns a RetryError wrapping the original error
438 ///
439 /// # Example
440 ///
441 /// ```rust
442 /// use prism3_retry::RetryError;
443 ///
444 /// let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
445 /// let boxed_error = Box::new(io_error);
446 /// let retry_error = RetryError::execution_error_box(boxed_error);
447 /// assert!(retry_error.to_string().contains("Execution error"));
448 /// ```
449 pub fn execution_error_box(error: Box<dyn Error + Send + Sync>) -> Self {
450 RetryError::ExecutionError { source: error }
451 }
452
453 /// Create other error
454 ///
455 /// Use this method to create an error for other error situations that don't fall into the above categories.
456 ///
457 /// # Parameters
458 ///
459 /// * `message` - Error description message
460 ///
461 /// # Returns
462 ///
463 /// Returns a RetryError containing error information
464 ///
465 /// # Example
466 ///
467 /// ```rust
468 /// use prism3_retry::RetryError;
469 ///
470 /// let error = RetryError::other("Unknown error type");
471 /// assert!(error.to_string().contains("Other error"));
472 /// ```
473 pub fn other(message: &str) -> Self {
474 RetryError::Other {
475 message: message.to_string(),
476 }
477 }
478}
479
480/// Retry result type alias
481///
482/// Represents the result of a retry operation, returning type T on success and RetryError on failure.
483/// This is the unified return type for all operations in the retry module.
484///
485/// # Type Parameters
486///
487/// * `T` - The data type returned on success
488///
489/// # Example
490///
491/// ```rust
492/// use prism3_retry::{RetryResult, RetryError};
493///
494/// fn retry_operation() -> RetryResult<String> {
495/// // Simulate retry operation
496/// Ok("Operation successful".to_string())
497/// }
498///
499/// fn retry_operation_failed() -> RetryResult<String> {
500/// Err(RetryError::other("Operation failed"))
501/// }
502/// ```
503pub type RetryResult<T> = Result<T, RetryError>;
504
505/// Convert from standard error types
506///
507/// Provides automatic conversion from std::io::Error to RetryError.
508/// This simplifies error handling, allowing direct use of the ? operator.
509///
510/// # Parameters
511///
512/// * `error` - IO error
513///
514/// # Returns
515///
516/// Returns a RetryError wrapping the IO error
517///
518/// # Example
519///
520/// ```rust
521/// use prism3_retry::{RetryError, RetryResult};
522///
523/// fn io_operation() -> RetryResult<()> {
524/// let file = std::fs::File::open("nonexistent_file.txt")?;
525/// // Do something with file
526/// Ok(())
527/// }
528/// ```
529impl From<std::io::Error> for RetryError {
530 /// Convert std::io::Error to RetryError
531 fn from(error: std::io::Error) -> Self {
532 RetryError::ExecutionError {
533 source: Box::new(error),
534 }
535 }
536}
537
538/// Convert from boxed error types
539///
540/// Provides automatic conversion from Box<dyn Error + Send + Sync> to RetryError.
541/// This allows converting any boxed error directly to RetryError.
542///
543/// # Parameters
544///
545/// * `error` - Boxed error
546///
547/// # Returns
548///
549/// Returns a RetryError wrapping the original error
550///
551/// # Example
552///
553/// ```rust
554/// use prism3_retry::RetryError;
555///
556/// let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
557/// let boxed_error: Box<dyn std::error::Error + Send + Sync> = Box::new(io_error);
558/// let retry_error: RetryError = boxed_error.into();
559/// ```
560impl From<Box<dyn Error + Send + Sync>> for RetryError {
561 /// Convert boxed error to RetryError
562 fn from(error: Box<dyn Error + Send + Sync>) -> Self {
563 RetryError::ExecutionError { source: error }
564 }
565}