apibrasil 1.0.0

SDK oficial Rust da plataforma APIBrasil: WhatsApp, SMS, consultas de CPF/CNPJ, veiculos, CEP, correios, pagamentos PIX/boleto e mais.
Documentation
//! Política de retry da SDK.
//!
//! Por padrão a SDK tenta novamente apenas em HTTP 429 (rate limit) e em
//! falhas de conexão — nunca em timeouts ou erros de negócio, para não
//! duplicar cobranças/envios.

use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

/// Política de retry do cliente.
///
/// ```
/// use std::time::Duration;
/// use apibrasil::{Config, RetryConfig};
///
/// let config = Config::new().retry(RetryConfig {
///     retries: 3,
///     min_delay: Duration::from_millis(500),
///     retry_on_statuses: vec![429, 503],
///     ..Default::default()
/// });
///
/// // ou desativando o retry
/// let config = Config::new().retry(RetryConfig::none());
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RetryConfig {
    /// Número de novas tentativas além da original. Padrão: 2.
    pub retries: u32,
    /// Atraso base do backoff exponencial. Padrão: 300ms.
    pub min_delay: Duration,
    /// Teto do atraso entre tentativas. Padrão: 5s.
    pub max_delay: Duration,
    /// Status HTTP que disparam retry. Padrão: `[429]`.
    pub retry_on_statuses: Vec<u16>,
}

impl Default for RetryConfig {
    fn default() -> Self {
        Self {
            retries: 2,
            min_delay: Duration::from_millis(300),
            max_delay: Duration::from_secs(5),
            retry_on_statuses: vec![429],
        }
    }
}

impl RetryConfig {
    /// Política que desativa o retry.
    pub fn none() -> Self {
        Self {
            retries: 0,
            ..Default::default()
        }
    }

    /// Número total de tentativas (a original mais os retries).
    pub const fn max_attempts(&self) -> u32 {
        self.retries + 1
    }

    /// Informa se um status HTTP dispara retry nesta política.
    pub fn retries_status(&self, status: u16) -> bool {
        self.retry_on_statuses.contains(&status)
    }

    /// Calcula o backoff exponencial com jitter:
    /// `min_delay * 2^attempt`, limitado a `max_delay`.
    pub fn backoff_delay(&self, attempt: u32) -> Duration {
        let exponential = self.min_delay.as_secs_f64() * 2f64.powi(attempt as i32);
        let delay = Duration::from_secs_f64(exponential * (0.5 + jitter() * 0.5));
        delay.min(self.max_delay)
    }
}

/// Aguarda `delay` — cancelável junto com o future da requisição.
pub(crate) async fn sleep(delay: Duration) {
    if !delay.is_zero() {
        tokio::time::sleep(delay).await;
    }
}

/// Jitter em `[0, 1)`. Usa um xorshift64* semeado pelo relógio — o
/// jitter não precisa de gerador criptográfico e evita uma dependência.
fn jitter() -> f64 {
    static STATE: AtomicU64 = AtomicU64::new(0);

    let mut state = STATE.load(Ordering::Relaxed);
    if state == 0 {
        state = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|elapsed| elapsed.as_nanos() as u64)
            .unwrap_or(0x9E37_79B9_7F4A_7C15)
            | 1;
    }

    state ^= state >> 12;
    state ^= state << 25;
    state ^= state >> 27;
    STATE.store(state, Ordering::Relaxed);

    let value = state.wrapping_mul(0x2545_F491_4F6C_DD1D);
    (value >> 11) as f64 / (1u64 << 53) as f64
}