use std::time::Duration;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum BackoffStrategy {
None,
#[default]
Linear,
Exponential,
}
impl BackoffStrategy {
pub fn delay(&self, attempt_index: u32) -> Duration {
match self {
BackoffStrategy::None => Duration::ZERO,
BackoffStrategy::Linear => {
let secs = (attempt_index + 1).min(10);
Duration::from_secs(u64::from(secs))
}
BackoffStrategy::Exponential => {
let secs = 2u64.saturating_pow(attempt_index).min(30);
Duration::from_secs(secs)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_backoff_always_zero() {
for attempt in 0..10 {
assert_eq!(BackoffStrategy::None.delay(attempt), Duration::ZERO);
}
}
#[test]
fn linear_backoff_increments_by_one() {
assert_eq!(BackoffStrategy::Linear.delay(0), Duration::from_secs(1));
assert_eq!(BackoffStrategy::Linear.delay(1), Duration::from_secs(2));
assert_eq!(BackoffStrategy::Linear.delay(2), Duration::from_secs(3));
}
#[test]
fn linear_backoff_caps_at_10() {
assert_eq!(BackoffStrategy::Linear.delay(9), Duration::from_secs(10));
assert_eq!(BackoffStrategy::Linear.delay(15), Duration::from_secs(10));
assert_eq!(BackoffStrategy::Linear.delay(100), Duration::from_secs(10));
}
#[test]
fn exponential_backoff_doubles() {
assert_eq!(
BackoffStrategy::Exponential.delay(0),
Duration::from_secs(1)
);
assert_eq!(
BackoffStrategy::Exponential.delay(1),
Duration::from_secs(2)
);
assert_eq!(
BackoffStrategy::Exponential.delay(2),
Duration::from_secs(4)
);
assert_eq!(
BackoffStrategy::Exponential.delay(3),
Duration::from_secs(8)
);
}
#[test]
fn exponential_backoff_caps_at_30() {
assert_eq!(
BackoffStrategy::Exponential.delay(5),
Duration::from_secs(30)
);
assert_eq!(
BackoffStrategy::Exponential.delay(10),
Duration::from_secs(30)
);
}
}