use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RetryStrategy {
ExponentialBackoff,
LinearBackoff,
FixedDelay,
NoRetry,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ExponentialBackoffConfig {
pub initial_delay_ms: u64,
pub max_delay_ms: u64,
pub multiplier: f64,
pub jitter: bool,
}
impl Default for ExponentialBackoffConfig {
fn default() -> Self {
Self {
initial_delay_ms: 1000,
max_delay_ms: 30000,
multiplier: 2.0,
jitter: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LinearBackoffConfig {
pub initial_delay_ms: u64,
pub increment_ms: u64,
pub max_delay_ms: u64,
}
impl Default for LinearBackoffConfig {
fn default() -> Self {
Self {
initial_delay_ms: 1000,
increment_ms: 1000,
max_delay_ms: 10000,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FixedDelayConfig {
pub delay_ms: u64,
}
impl Default for FixedDelayConfig {
fn default() -> Self {
Self {
delay_ms: 1000,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RetryPolicy {
pub max_retries: usize,
pub strategy: RetryStrategy,
pub exponential_config: ExponentialBackoffConfig,
pub linear_config: LinearBackoffConfig,
pub fixed_config: FixedDelayConfig,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self {
max_retries: 3,
strategy: RetryStrategy::ExponentialBackoff,
exponential_config: ExponentialBackoffConfig::default(),
linear_config: LinearBackoffConfig::default(),
fixed_config: FixedDelayConfig::default(),
}
}
}
impl RetryPolicy {
pub fn new(max_retries: usize, strategy: RetryStrategy) -> Self {
Self {
max_retries,
strategy,
exponential_config: ExponentialBackoffConfig::default(),
linear_config: LinearBackoffConfig::default(),
fixed_config: FixedDelayConfig::default(),
}
}
pub fn calculate_delay(&self, attempt: usize) -> Duration {
if attempt == 0 {
return Duration::from_millis(0);
}
match self.strategy {
RetryStrategy::ExponentialBackoff => {
let delay_ms = (self.exponential_config.initial_delay_ms as f64
* self.exponential_config.multiplier.powi(attempt as i32 - 1))
.min(self.exponential_config.max_delay_ms as f64) as u64;
let mut delay = Duration::from_millis(delay_ms);
if self.exponential_config.jitter {
let jitter_range = delay_ms / 4;
let jitter = (rand::random::<u64>() % (jitter_range * 2)) as i64 - jitter_range as i64;
let adjusted_ms = (delay_ms as i64 + jitter).max(0) as u64;
delay = Duration::from_millis(adjusted_ms);
}
delay
}
RetryStrategy::LinearBackoff => {
let delay_ms = (self.linear_config.initial_delay_ms
+ (attempt - 1) as u64 * self.linear_config.increment_ms)
.min(self.linear_config.max_delay_ms);
Duration::from_millis(delay_ms)
}
RetryStrategy::FixedDelay => {
Duration::from_millis(self.fixed_config.delay_ms)
}
RetryStrategy::NoRetry => {
Duration::from_millis(0)
}
}
}
pub fn should_retry(&self, attempt: usize) -> bool {
attempt < self.max_retries && self.strategy != RetryStrategy::NoRetry
}
pub fn max_retries(&self) -> usize {
self.max_retries
}
pub fn strategy(&self) -> &RetryStrategy {
&self.strategy
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_retry_policy_creation() {
let policy = RetryPolicy::new(5, RetryStrategy::ExponentialBackoff);
assert_eq!(policy.max_retries(), 5);
assert_eq!(policy.strategy(), &RetryStrategy::ExponentialBackoff);
}
#[test]
fn test_retry_policy_default() {
let policy = RetryPolicy::default();
assert_eq!(policy.max_retries(), 3);
assert_eq!(policy.strategy(), &RetryStrategy::ExponentialBackoff);
}
#[test]
fn test_should_retry() {
let policy = RetryPolicy::new(3, RetryStrategy::ExponentialBackoff);
assert!(policy.should_retry(0));
assert!(policy.should_retry(1));
assert!(policy.should_retry(2));
assert!(!policy.should_retry(3));
assert!(!policy.should_retry(4));
}
#[test]
fn test_should_retry_no_retry_strategy() {
let policy = RetryPolicy::new(3, RetryStrategy::NoRetry);
assert!(!policy.should_retry(0));
assert!(!policy.should_retry(1));
assert!(!policy.should_retry(2));
}
#[test]
fn test_calculate_delay_exponential() {
let policy = RetryPolicy::new(5, RetryStrategy::ExponentialBackoff);
let delay0 = policy.calculate_delay(0);
assert_eq!(delay0, Duration::from_millis(0));
let delay1 = policy.calculate_delay(1);
assert_eq!(delay1, Duration::from_millis(1000));
let delay2 = policy.calculate_delay(2);
assert_eq!(delay2, Duration::from_millis(2000));
let delay3 = policy.calculate_delay(3);
assert_eq!(delay3, Duration::from_millis(4000)); }
#[test]
fn test_calculate_delay_linear() {
let policy = RetryPolicy::new(5, RetryStrategy::LinearBackoff);
let delay0 = policy.calculate_delay(0);
assert_eq!(delay0, Duration::from_millis(0));
let delay1 = policy.calculate_delay(1);
assert_eq!(delay1, Duration::from_millis(1000));
let delay2 = policy.calculate_delay(2);
assert_eq!(delay2, Duration::from_millis(2000));
let delay3 = policy.calculate_delay(3);
assert_eq!(delay3, Duration::from_millis(3000)); }
#[test]
fn test_calculate_delay_fixed() {
let policy = RetryPolicy::new(5, RetryStrategy::FixedDelay);
let delay0 = policy.calculate_delay(0);
assert_eq!(delay0, Duration::from_millis(0));
let delay1 = policy.calculate_delay(1);
assert_eq!(delay1, Duration::from_millis(1000));
let delay2 = policy.calculate_delay(2);
assert_eq!(delay2, Duration::from_millis(1000));
let delay3 = policy.calculate_delay(3);
assert_eq!(delay3, Duration::from_millis(1000)); }
#[test]
fn test_calculate_delay_no_retry() {
let policy = RetryPolicy::new(5, RetryStrategy::NoRetry);
let delay0 = policy.calculate_delay(0);
assert_eq!(delay0, Duration::from_millis(0));
let delay1 = policy.calculate_delay(1);
assert_eq!(delay1, Duration::from_millis(0));
let delay2 = policy.calculate_delay(2);
assert_eq!(delay2, Duration::from_millis(0));
}
#[test]
fn test_max_delay_limit() {
let mut policy = RetryPolicy::new(10, RetryStrategy::ExponentialBackoff);
policy.exponential_config.max_delay_ms = 5000;
let delay = policy.calculate_delay(10);
assert!(delay <= Duration::from_millis(5000));
}
}