use super::definition::{BackoffType, RetryStrategy};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetryConfig {
pub max_attempts: usize,
pub initial_delay_ms: u64,
pub max_delay_ms: u64,
pub multiplier: f64,
pub strategy: BackoffStrategy,
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_attempts: 3,
initial_delay_ms: 1000,
max_delay_ms: 60_000,
multiplier: 2.0,
strategy: BackoffStrategy::Exponential,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum BackoffStrategy {
Fixed,
Linear,
Exponential,
ExponentialJitter,
}
pub struct RetryManager;
impl RetryManager {
pub fn new() -> Self {
Self
}
pub fn calculate_delay(&self, strategy: &RetryStrategy, attempt: usize) -> u64 {
let backoff_strategy = match strategy.backoff {
BackoffType::Fixed => BackoffStrategy::Fixed,
BackoffType::Linear => BackoffStrategy::Linear,
BackoffType::Exponential => BackoffStrategy::Exponential,
BackoffType::ExponentialJitter => BackoffStrategy::ExponentialJitter,
};
let delay = match backoff_strategy {
BackoffStrategy::Fixed => strategy.initial_delay_ms,
BackoffStrategy::Linear => strategy.initial_delay_ms * attempt as u64,
BackoffStrategy::Exponential => {
let delay = strategy.initial_delay_ms
* (strategy.backoff_multiplier.powi(attempt as i32 - 1) as u64);
delay.min(strategy.max_delay_ms)
}
BackoffStrategy::ExponentialJitter => {
let base_delay = strategy.initial_delay_ms
* (strategy.backoff_multiplier.powi(attempt as i32 - 1) as u64);
let jitter = (base_delay as f64 * 0.1) as u64;
(base_delay + jitter).min(strategy.max_delay_ms)
}
};
delay
.max(strategy.initial_delay_ms)
.min(strategy.max_delay_ms)
}
pub fn should_retry(&self, attempt: usize, max_attempts: usize) -> bool {
attempt < max_attempts
}
}
impl Default for RetryManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_retry_config_default() {
let config = RetryConfig::default();
assert_eq!(config.max_attempts, 3);
assert_eq!(config.initial_delay_ms, 1000);
assert_eq!(config.max_delay_ms, 60_000);
}
#[test]
fn test_retry_manager_creation() {
let manager = RetryManager::new();
assert!(manager.should_retry(1, 3));
}
#[test]
fn test_should_retry() {
let manager = RetryManager::new();
assert!(manager.should_retry(1, 3));
assert!(manager.should_retry(2, 3));
assert!(!manager.should_retry(3, 3));
assert!(!manager.should_retry(4, 3));
}
#[test]
fn test_fixed_backoff() {
let manager = RetryManager::new();
let strategy = RetryStrategy {
max_attempts: 3,
backoff: BackoffType::Fixed,
initial_delay_ms: 1000,
max_delay_ms: 60_000,
backoff_multiplier: 2.0,
};
let delay1 = manager.calculate_delay(&strategy, 1);
let delay2 = manager.calculate_delay(&strategy, 2);
let delay3 = manager.calculate_delay(&strategy, 3);
assert_eq!(delay1, 1000);
assert_eq!(delay2, 1000);
assert_eq!(delay3, 1000);
}
#[test]
fn test_linear_backoff() {
let manager = RetryManager::new();
let strategy = RetryStrategy {
max_attempts: 3,
backoff: BackoffType::Linear,
initial_delay_ms: 1000,
max_delay_ms: 60_000,
backoff_multiplier: 2.0,
};
let delay1 = manager.calculate_delay(&strategy, 1);
let delay2 = manager.calculate_delay(&strategy, 2);
let delay3 = manager.calculate_delay(&strategy, 3);
assert_eq!(delay1, 1000);
assert_eq!(delay2, 2000);
assert_eq!(delay3, 3000);
}
#[test]
fn test_exponential_backoff() {
let manager = RetryManager::new();
let strategy = RetryStrategy {
max_attempts: 4,
backoff: BackoffType::Exponential,
initial_delay_ms: 1000,
max_delay_ms: 60_000,
backoff_multiplier: 2.0,
};
let delay1 = manager.calculate_delay(&strategy, 1);
let delay2 = manager.calculate_delay(&strategy, 2);
let delay3 = manager.calculate_delay(&strategy, 3);
assert_eq!(delay1, 1000);
assert_eq!(delay2, 2000);
assert_eq!(delay3, 4000);
}
#[test]
fn test_max_delay_cap() {
let manager = RetryManager::new();
let strategy = RetryStrategy {
max_attempts: 10,
backoff: BackoffType::Exponential,
initial_delay_ms: 1000,
max_delay_ms: 5000,
backoff_multiplier: 2.0,
};
let delay5 = manager.calculate_delay(&strategy, 5);
assert!(delay5 <= 5000);
}
}