arcly-http 0.1.1

Enterprise-grade NestJS-inspired web framework on axum: zero-lock DI, declarative controllers, multi-tenant data routing, transactional outbox, ABAC, and a self-documenting OpenAPI surface
Documentation
//! Lock-free fixed-window rate limiter.
//!
//! Lives under `resilience` (not `auth/guards`) because rate limiting is a
//! load-shedding concern, independent of who the caller is. It still
//! implements [`Guard`] so it composes with auth guards at handler level:
//!
//! ```ignore
//! static PUBLIC_RATE: RateLimit = RateLimit::new(200, 60);
//! PUBLIC_RATE.check(&ctx)?;
//! ```

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

use crate::auth::guards::Guard;
use crate::web::{Error, RequestContext};

/// Per-instance fixed-window limiter. Zero locks: a `(window_start, count)` pair
/// is packed into a single `AtomicU64` and updated via CAS.
pub struct RateLimit {
    state: AtomicU64, // high 32 bits = window-start seconds, low 32 = count
    window_secs: u32,
    max_per_window: u32,
}

impl RateLimit {
    pub const fn new(max_per_window: u32, window_secs: u32) -> Self {
        Self {
            state: AtomicU64::new(0),
            window_secs,
            max_per_window,
        }
    }

    fn now_secs() -> u32 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|d| d.as_secs() as u32)
            .unwrap_or(0)
    }
}

impl Guard for RateLimit {
    fn check(&self, _ctx: &RequestContext) -> Result<(), Error> {
        let now = Self::now_secs();
        loop {
            let cur = self.state.load(Relaxed);
            let (start, count) = ((cur >> 32) as u32, cur as u32);
            let (new_start, new_count) = if now.saturating_sub(start) >= self.window_secs {
                (now, 1)
            } else {
                (start, count.saturating_add(1))
            };
            if new_count > self.max_per_window {
                return Err(Error::TooManyRequests);
            }
            let next = ((new_start as u64) << 32) | (new_count as u64);
            if self
                .state
                .compare_exchange(cur, next, Relaxed, Relaxed)
                .is_ok()
            {
                return Ok(());
            }
        }
    }
}