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}