use std::time::Duration;
fn is_status_client_error(status: u16) -> bool {
(400..500).contains(&status)
}
#[derive(Clone, Debug)]
pub enum RequestStrategy {
Once,
Retry(u32),
ExponentialBackoff(u32),
}
impl RequestStrategy {
pub fn test(
&self,
status: Option<u16>,
retry_count: u32,
) -> Outcome {
use RequestStrategy::*;
match (self, status, retry_count) {
(Once, _, 0) => Outcome::Continue(None),
(_, Some(c), _) if is_status_client_error(c) => Outcome::Stop,
(Retry(n), _, x) if x < *n => Outcome::Continue(None),
(ExponentialBackoff(n), _, x) if x < *n => {
Outcome::Continue(Some(calculate_backoff(x)))
}
_ => Outcome::Stop,
}
}
pub fn get_key(&self) -> Option<String> {
match self {
RequestStrategy::Once => None,
#[cfg(feature = "uuid")]
RequestStrategy::Retry(_) | RequestStrategy::ExponentialBackoff(_) => {
Some(uuid::Uuid::new_v4().to_string())
}
#[cfg(not(feature = "uuid"))]
RequestStrategy::Retry(_) | RequestStrategy::ExponentialBackoff(_) => None,
}
}
}
fn calculate_backoff(retry_count: u32) -> Duration {
Duration::from_secs(2_u64.pow(retry_count))
}
#[derive(PartialEq, Eq, Debug)]
pub enum Outcome {
Stop,
Continue(Option<Duration>),
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::{Outcome, RequestStrategy};
#[test]
fn test_once_strategy() {
let strategy = RequestStrategy::Once;
assert_eq!(strategy.get_key(), None);
assert_eq!(strategy.test(None, 0), Outcome::Continue(None));
assert_eq!(strategy.test(None, 1), Outcome::Stop);
}
#[test]
#[cfg(feature = "uuid")]
fn test_uuid_idempotency() {
use uuid::Uuid;
let strategy = RequestStrategy::Retry(3);
assert!(Uuid::parse_str(&strategy.get_key().unwrap()).is_ok());
}
#[test]
#[cfg(not(feature = "uuid"))]
fn test_uuid_idempotency() {
let strategy = RequestStrategy::Retry(3);
assert_eq!(strategy.get_key(), None);
}
#[test]
fn test_retry_strategy() {
let strategy = RequestStrategy::Retry(3);
assert_eq!(strategy.test(None, 0), Outcome::Continue(None));
assert_eq!(strategy.test(None, 1), Outcome::Continue(None));
assert_eq!(strategy.test(None, 2), Outcome::Continue(None));
assert_eq!(strategy.test(None, 3), Outcome::Stop);
assert_eq!(strategy.test(None, 4), Outcome::Stop);
}
#[test]
fn test_backoff_strategy() {
let strategy = RequestStrategy::ExponentialBackoff(3);
assert_eq!(strategy.test(None, 0), Outcome::Continue(Some(Duration::from_secs(1))));
assert_eq!(strategy.test(None, 1), Outcome::Continue(Some(Duration::from_secs(2))));
assert_eq!(strategy.test(None, 2), Outcome::Continue(Some(Duration::from_secs(4))));
assert_eq!(strategy.test(None, 3), Outcome::Stop);
assert_eq!(strategy.test(None, 4), Outcome::Stop);
}
#[test]
fn test_retry_header() {
let strategy = RequestStrategy::Retry(3);
assert_eq!(strategy.test(None, 0), Outcome::Stop);
}
}