use std::time::Duration;
use qubit_config::{ConfigReader, ConfigResult};
use super::retry_delay::RetryDelay;
use super::retry_jitter::RetryJitter;
use super::retry_options::RetryOptions;
use crate::RetryConfigError;
use crate::constants::{
DEFAULT_RETRY_EXPONENTIAL_INITIAL_DELAY_MILLIS, DEFAULT_RETRY_EXPONENTIAL_MAX_DELAY_MILLIS,
DEFAULT_RETRY_EXPONENTIAL_MULTIPLIER, DEFAULT_RETRY_FIXED_DELAY_MILLIS,
DEFAULT_RETRY_JITTER_FACTOR, DEFAULT_RETRY_RANDOM_MAX_DELAY_MILLIS,
DEFAULT_RETRY_RANDOM_MIN_DELAY_MILLIS, KEY_DELAY, KEY_DELAY_STRATEGY,
KEY_EXPONENTIAL_INITIAL_DELAY_MILLIS, KEY_EXPONENTIAL_MAX_DELAY_MILLIS,
KEY_EXPONENTIAL_MULTIPLIER, KEY_FIXED_DELAY_MILLIS, KEY_JITTER_FACTOR, KEY_MAX_ATTEMPTS,
KEY_MAX_ELAPSED_MILLIS, KEY_MAX_ELAPSED_UNLIMITED, KEY_RANDOM_MAX_DELAY_MILLIS,
KEY_RANDOM_MIN_DELAY_MILLIS,
};
#[derive(Debug, Clone, PartialEq)]
pub struct RetryConfigValues {
pub max_attempts: Option<u32>,
pub max_elapsed_millis: Option<u64>,
pub max_elapsed_unlimited: Option<bool>,
pub delay: Option<String>,
pub delay_strategy: Option<String>,
pub fixed_delay_millis: Option<u64>,
pub random_min_delay_millis: Option<u64>,
pub random_max_delay_millis: Option<u64>,
pub exponential_initial_delay_millis: Option<u64>,
pub exponential_max_delay_millis: Option<u64>,
pub exponential_multiplier: Option<f64>,
pub jitter_factor: Option<f64>,
}
impl RetryConfigValues {
pub(crate) fn new<R>(config: &R) -> ConfigResult<Self>
where
R: ConfigReader + ?Sized,
{
Ok(Self {
max_attempts: config.get_optional(KEY_MAX_ATTEMPTS)?,
max_elapsed_millis: config.get_optional(KEY_MAX_ELAPSED_MILLIS)?,
max_elapsed_unlimited: config.get_optional(KEY_MAX_ELAPSED_UNLIMITED)?,
delay: config.get_optional_string(KEY_DELAY)?,
delay_strategy: config.get_optional_string(KEY_DELAY_STRATEGY)?,
fixed_delay_millis: config.get_optional(KEY_FIXED_DELAY_MILLIS)?,
random_min_delay_millis: config.get_optional(KEY_RANDOM_MIN_DELAY_MILLIS)?,
random_max_delay_millis: config.get_optional(KEY_RANDOM_MAX_DELAY_MILLIS)?,
exponential_initial_delay_millis: config
.get_optional(KEY_EXPONENTIAL_INITIAL_DELAY_MILLIS)?,
exponential_max_delay_millis: config.get_optional(KEY_EXPONENTIAL_MAX_DELAY_MILLIS)?,
exponential_multiplier: config.get_optional(KEY_EXPONENTIAL_MULTIPLIER)?,
jitter_factor: config.get_optional(KEY_JITTER_FACTOR)?,
})
}
pub fn to_options(&self, default: &RetryOptions) -> Result<RetryOptions, RetryConfigError> {
let max_attempts = self.max_attempts.unwrap_or(default.max_attempts());
let max_elapsed = self.get_max_elapsed(default);
let delay = self.get_delay(default)?;
let jitter = self.get_jitter(default);
RetryOptions::new(max_attempts, max_elapsed, delay, jitter)
}
fn get_max_elapsed(&self, default: &RetryOptions) -> Option<Duration> {
if self.max_elapsed_unlimited.unwrap_or(false) {
return None;
}
match self.max_elapsed_millis {
Some(millis) => Some(Duration::from_millis(millis)),
None => default.max_elapsed(),
}
}
fn get_delay(&self, default: &RetryOptions) -> Result<RetryDelay, RetryConfigError> {
let strategy = self
.delay
.as_deref()
.or(self.delay_strategy.as_deref())
.map(str::trim)
.map(|value| value.to_ascii_lowercase());
match strategy.as_deref() {
None => Ok(self
.get_implicit_delay()
.unwrap_or_else(|| default.delay().clone())),
Some("none") => Ok(RetryDelay::None),
Some("fixed") => Ok(RetryDelay::fixed(Duration::from_millis(
self.fixed_delay_millis
.unwrap_or(DEFAULT_RETRY_FIXED_DELAY_MILLIS),
))),
Some("random") => Ok(RetryDelay::random(
Duration::from_millis(
self.random_min_delay_millis
.unwrap_or(DEFAULT_RETRY_RANDOM_MIN_DELAY_MILLIS),
),
Duration::from_millis(
self.random_max_delay_millis
.unwrap_or(DEFAULT_RETRY_RANDOM_MAX_DELAY_MILLIS),
),
)),
Some("exponential") | Some("exponential_backoff") => Ok(RetryDelay::exponential(
Duration::from_millis(
self.exponential_initial_delay_millis
.unwrap_or(DEFAULT_RETRY_EXPONENTIAL_INITIAL_DELAY_MILLIS),
),
Duration::from_millis(
self.exponential_max_delay_millis
.unwrap_or(DEFAULT_RETRY_EXPONENTIAL_MAX_DELAY_MILLIS),
),
self.exponential_multiplier
.unwrap_or(DEFAULT_RETRY_EXPONENTIAL_MULTIPLIER),
)),
Some(other) => Err(RetryConfigError::invalid_value(
KEY_DELAY,
format!("unsupported delay strategy '{other}'"),
)),
}
}
fn get_implicit_delay(&self) -> Option<RetryDelay> {
if let Some(millis) = self.fixed_delay_millis {
return Some(RetryDelay::fixed(Duration::from_millis(millis)));
}
if self.random_min_delay_millis.is_some() || self.random_max_delay_millis.is_some() {
return Some(RetryDelay::random(
Duration::from_millis(
self.random_min_delay_millis
.unwrap_or(DEFAULT_RETRY_RANDOM_MIN_DELAY_MILLIS),
),
Duration::from_millis(
self.random_max_delay_millis
.unwrap_or(DEFAULT_RETRY_RANDOM_MAX_DELAY_MILLIS),
),
));
}
if self.exponential_initial_delay_millis.is_some()
|| self.exponential_max_delay_millis.is_some()
|| self.exponential_multiplier.is_some()
{
return Some(RetryDelay::exponential(
Duration::from_millis(
self.exponential_initial_delay_millis
.unwrap_or(DEFAULT_RETRY_EXPONENTIAL_INITIAL_DELAY_MILLIS),
),
Duration::from_millis(
self.exponential_max_delay_millis
.unwrap_or(DEFAULT_RETRY_EXPONENTIAL_MAX_DELAY_MILLIS),
),
self.exponential_multiplier
.unwrap_or(DEFAULT_RETRY_EXPONENTIAL_MULTIPLIER),
));
}
None
}
fn get_jitter(&self, default: &RetryOptions) -> RetryJitter {
match self.jitter_factor {
Some(factor) if factor == DEFAULT_RETRY_JITTER_FACTOR => RetryJitter::None,
None => default.jitter(),
Some(factor) => RetryJitter::Factor(factor),
}
}
}