use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RetryExhausted<E> {
pub final_error: E,
pub attempts: u32,
pub total_duration: Duration,
}
impl<E> RetryExhausted<E> {
pub fn new(final_error: E, attempts: u32, total_duration: Duration) -> Self {
Self {
final_error,
attempts,
total_duration,
}
}
pub fn into_error(self) -> E {
self.final_error
}
pub fn error(&self) -> &E {
&self.final_error
}
pub fn into_value(self) -> E {
self.final_error
}
}
impl<E: std::fmt::Display> std::fmt::Display for RetryExhausted<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"retry exhausted after {} attempts ({:?}): {}",
self.attempts, self.total_duration, self.final_error
)
}
}
impl<E: std::error::Error + 'static> std::error::Error for RetryExhausted<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.final_error)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TimeoutError<E> {
Timeout {
duration: Duration,
},
Inner(E),
}
impl<E> TimeoutError<E> {
pub fn timeout(duration: Duration) -> Self {
Self::Timeout { duration }
}
pub fn inner(error: E) -> Self {
Self::Inner(error)
}
pub fn is_timeout(&self) -> bool {
matches!(self, Self::Timeout { .. })
}
pub fn is_inner(&self) -> bool {
matches!(self, Self::Inner(_))
}
pub fn into_inner(self) -> Option<E> {
match self {
Self::Inner(e) => Some(e),
Self::Timeout { .. } => None,
}
}
}
impl<E: std::fmt::Display> std::fmt::Display for TimeoutError<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Timeout { duration } => write!(f, "operation timed out after {:?}", duration),
Self::Inner(e) => write!(f, "{}", e),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for TimeoutError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Timeout { .. } => None,
Self::Inner(e) => Some(e),
}
}
}
#[cfg(test)]
mod error_tests {
use super::*;
#[test]
fn test_retry_exhausted_display() {
let err = RetryExhausted::new("connection failed", 3, Duration::from_millis(500));
let display = format!("{}", err);
assert!(display.contains("retry exhausted"));
assert!(display.contains("3 attempts"));
assert!(display.contains("connection failed"));
}
#[test]
fn test_retry_exhausted_into_error() {
let err = RetryExhausted::new("test error", 5, Duration::from_secs(1));
assert_eq!(err.into_error(), "test error");
}
#[test]
fn test_timeout_error_timeout() {
let err: TimeoutError<String> = TimeoutError::timeout(Duration::from_secs(5));
assert!(err.is_timeout());
assert!(!err.is_inner());
assert!(err.into_inner().is_none());
}
#[test]
fn test_timeout_error_inner() {
let err = TimeoutError::inner("inner error".to_string());
assert!(!err.is_timeout());
assert!(err.is_inner());
assert_eq!(err.into_inner(), Some("inner error".to_string()));
}
#[test]
fn test_timeout_error_display() {
let timeout: TimeoutError<String> = TimeoutError::timeout(Duration::from_secs(5));
assert!(format!("{}", timeout).contains("timed out"));
let inner = TimeoutError::inner("failed".to_string());
assert_eq!(format!("{}", inner), "failed");
}
}