technitium 0.4.0

Typed async Rust client for the Technitium DNS Server API
Documentation
use std::time::Duration;

use rand::RngExt;

/// Configuration for automatic retry with exponential backoff.
///
/// # Examples
///
/// ```
/// use std::time::Duration;
/// use technitium::RetryPolicy;
///
/// // Use defaults: 3 attempts, 1s base, 30s max
/// let policy = RetryPolicy::default();
///
/// // Custom policy
/// let policy = RetryPolicy {
///     max_attempts: 5,
///     base_delay: Duration::from_millis(500),
///     max_delay: Duration::from_secs(10),
/// };
/// ```
#[derive(Debug, Clone)]
pub struct RetryPolicy {
    /// Maximum number of retry attempts. 0 disables retry.
    pub max_attempts: u32,
    /// Base delay for exponential backoff.
    pub base_delay: Duration,
    /// Maximum delay cap.
    pub max_delay: Duration,
}

impl Default for RetryPolicy {
    fn default() -> Self {
        Self {
            max_attempts: 3,
            base_delay: Duration::from_secs(1),
            max_delay: Duration::from_secs(30),
        }
    }
}

impl RetryPolicy {
    /// Compute the backoff delay for the given attempt number.
    ///
    /// Uses exponential backoff with full jitter:
    /// `delay = random(0, min(max_delay, base_delay * 2^attempt))`
    #[must_use]
    pub fn compute_delay(&self, attempt: u32) -> Duration {
        let exp = self
            .base_delay
            .as_millis()
            .saturating_mul(1u128 << attempt.min(20));
        let capped = exp.min(self.max_delay.as_millis());
        if capped == 0 {
            return Duration::ZERO;
        }
        let jittered = rand::rng().random_range(0..=capped);
        Duration::from_millis(u64::try_from(jittered).unwrap_or(u64::MAX))
    }
}