use std::num::NonZeroU32;
use std::time::Duration;
use crate::core::ConfigError;
use crate::policies::{BackoffPolicy, RestartPolicy};
#[inline]
fn normalize_timeout(timeout: Option<Duration>) -> Option<Duration> {
timeout.filter(|duration| !duration.is_zero())
}
#[derive(Clone, Debug)]
#[must_use]
pub struct TaskDefaults {
restart: RestartPolicy,
backoff: BackoffPolicy,
timeout: Option<Duration>,
max_retries: Option<NonZeroU32>,
}
impl TaskDefaults {
#[must_use]
pub fn restart(&self) -> RestartPolicy {
self.restart
}
#[must_use]
pub fn backoff(&self) -> BackoffPolicy {
self.backoff
}
#[must_use]
pub fn timeout(&self) -> Option<Duration> {
self.timeout
}
#[must_use]
pub fn max_retries(&self) -> Option<NonZeroU32> {
self.max_retries
}
pub fn with_restart(mut self, restart: RestartPolicy) -> Self {
self.restart = restart;
self
}
pub fn with_backoff(mut self, backoff: BackoffPolicy) -> Self {
self.backoff = backoff;
self
}
pub fn with_timeout(mut self, timeout: impl Into<Option<Duration>>) -> Self {
self.timeout = normalize_timeout(timeout.into());
self
}
pub fn with_max_retries(mut self, max_retries: impl Into<Option<NonZeroU32>>) -> Self {
self.max_retries = max_retries.into();
self
}
pub fn try_with_max_retries(self, max_retries: u32) -> Result<Self, ConfigError> {
let max_retries = NonZeroU32::new(max_retries).ok_or(ConfigError::Zero {
field: "max_retries",
})?;
Ok(self.with_max_retries(max_retries))
}
}
impl Default for TaskDefaults {
fn default() -> Self {
Self {
restart: RestartPolicy::default(),
backoff: BackoffPolicy::default(),
timeout: None,
max_retries: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::JitterPolicy;
#[test]
fn default_contract_is_explicit_and_safe() {
let defaults = TaskDefaults::default();
assert!(matches!(defaults.restart(), RestartPolicy::OnFailure));
assert_eq!(defaults.backoff().first(), Duration::from_millis(200));
assert_eq!(defaults.backoff().factor(), 2.0);
assert_eq!(defaults.backoff().max(), Duration::from_secs(30));
assert_eq!(defaults.backoff().jitter(), JitterPolicy::Equal);
assert_eq!(defaults.timeout(), None);
assert_eq!(defaults.max_retries(), None);
}
#[test]
fn builders_replace_each_default() {
let backoff = BackoffPolicy::constant(Duration::from_secs(2));
let retries = NonZeroU32::new(4).unwrap();
let defaults = TaskDefaults::default()
.with_restart(RestartPolicy::Never)
.with_backoff(backoff)
.with_timeout(Duration::from_secs(10))
.with_max_retries(retries);
assert!(matches!(defaults.restart(), RestartPolicy::Never));
assert_eq!(defaults.backoff().first(), Duration::from_secs(2));
assert_eq!(defaults.timeout(), Some(Duration::from_secs(10)));
assert_eq!(defaults.max_retries(), Some(retries));
}
#[test]
fn optional_timeout_normalizes_zero_and_allows_none_to_clear() {
for defaults in [
TaskDefaults::default().with_timeout(Duration::ZERO),
TaskDefaults::default().with_timeout(Some(Duration::ZERO)),
TaskDefaults::default()
.with_timeout(Duration::from_secs(1))
.with_timeout(None),
] {
assert_eq!(defaults.timeout(), None);
}
assert_eq!(
TaskDefaults::default()
.with_timeout(Duration::from_secs(1))
.timeout(),
Some(Duration::from_secs(1))
);
}
#[test]
fn raw_zero_retry_limit_returns_a_clear_error() {
assert_eq!(
TaskDefaults::default().try_with_max_retries(0).unwrap_err(),
ConfigError::Zero {
field: "max_retries"
}
);
}
}