rate-guard 0.1.0

Thread-safe rate limiting library with multiple algorithms and Duration-based configuration
Documentation
//! Approximate sliding window rate limiter implementation with integrated time source and Builder pattern.
//!
//! This module provides an approximate sliding window rate limiter that provides
//! memory-efficient approximation of sliding windows. The implementation integrates time source
//! management directly and uses a Builder pattern for configuration.
//!
//! # Examples
//!
//! ```rust
//! use rate_guard::{Millis, MockTimeSource, RateLimit};
//! use rate_guard::limits::ApproximateSlidingWindowBuilder;
//! use std::time::Duration;
//!
//! let window = ApproximateSlidingWindowBuilder::builder()
//!     .capacity(100)
//!     .window_duration(Duration::from_secs(60))
//!     .with_time(MockTimeSource::new())
//!     .with_precision::<Millis>()
//!     .build()
//!     .unwrap();
//!
//! assert!(window.try_acquire(50).is_ok());
//! ```

use std::time::Duration;
use std::marker::PhantomData;
use crate::limits::RateLimit;
use crate::{Uint, SimpleRateLimitResult, RateLimitResult, BuildResult};
use crate::precision::Precision;
use crate::time_source::TimeSource;
use crate::error::{RateLimitError, BuildError};
use rate_guard_core::cores::ApproximateSlidingWindowCore;
use rate_guard_core::SimpleRateLimitError;

/// Approximate sliding window rate limiter with integrated time source and precision handling.
///
/// The approximate sliding window algorithm provides a memory-efficient approximation
/// of sliding window behavior. It uses less memory than the precise sliding window
/// counter while still providing reasonable sliding window characteristics.
///
/// # Type Parameters
/// * `P` - The precision type that determines how Duration values are converted to internal ticks
/// * `T` - The time source type that provides elapsed time since the rate limiter's reference point
pub struct ApproximateSlidingWindow<P: Precision, T: TimeSource> {
    /// The underlying core implementation that handles the approximate sliding window logic.
    core: ApproximateSlidingWindowCore,
    /// The time source for getting current elapsed time.
    time_source: T,
    /// Phantom data to associate the Precision type with this instance.
    _precision: PhantomData<P>,
}

/// Builder for configuring and creating ApproximateSlidingWindow instances.
///
/// ApproximateSlidingWindowBuilder provides a fluent interface for configuring all aspects
/// of an approximate sliding window rate limiter. The precision and time source types are
/// determined through the builder chain.
#[derive(Debug)]
pub struct ApproximateSlidingWindowBuilder {
    /// Maximum number of actions allowed in the sliding window.
    capacity: Option<Uint>,
    /// Total size of the sliding window duration.
    window_duration: Option<Duration>,
}

/// Builder with time source configured.
///
/// This intermediate builder is created after calling `with_time()` and allows
/// setting the precision type.
pub struct ApproximateSlidingWindowBuilderWithTime<T: TimeSource> {
    /// Maximum number of actions allowed in the sliding window.
    capacity: Option<Uint>,
    /// Total size of the sliding window duration.
    window_duration: Option<Duration>,
    /// Time source instance for elapsed time management.
    time_source: T,
}

/// Fully configured builder for ApproximateSlidingWindow with precision and time source set.
///
/// This builder is created after calling `with_precision()` and is ready to build
/// the final ApproximateSlidingWindow instance.
pub struct ConfiguredApproximateSlidingWindowBuilder<P: Precision, T: TimeSource> {
    /// Maximum number of actions allowed in the sliding window.
    capacity: Option<Uint>,
    /// Total size of the sliding window duration.
    window_duration: Option<Duration>,
    /// Time source instance for elapsed time management.
    time_source: T,
    /// Phantom data to associate the Precision type with this instance.
    _precision: PhantomData<P>,
}

impl<P: Precision, T: TimeSource> std::fmt::Debug for ApproximateSlidingWindow<P, T>
where
    T: std::fmt::Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ApproximateSlidingWindow")
            .field("time_source", &self.time_source)
            .field("_precision", &std::any::type_name::<P>())
            .finish_non_exhaustive()
    }
}

impl<T: TimeSource> std::fmt::Debug for ApproximateSlidingWindowBuilderWithTime<T>
where
    T: std::fmt::Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ApproximateSlidingWindowBuilderWithTime")
            .field("capacity", &self.capacity)
            .field("window_duration", &self.window_duration)
            .field("time_source", &self.time_source)
            .finish()
    }
}

impl<P: Precision, T: TimeSource> std::fmt::Debug for ConfiguredApproximateSlidingWindowBuilder<P, T>
where
    T: std::fmt::Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ConfiguredApproximateSlidingWindowBuilder")
            .field("capacity", &self.capacity)
            .field("window_duration", &self.window_duration)
            .field("time_source", &self.time_source)
            .field("_precision", &std::any::type_name::<P>())
            .finish()
    }
}

impl<P: Precision, T: TimeSource> RateLimit for ApproximateSlidingWindow<P, T> {
    #[inline(always)]
    fn try_acquire(&self, tokens: Uint) -> SimpleRateLimitResult {
        let elapsed = self.time_source.now();
        let current_tick = P::to_ticks(elapsed);
        self.core.try_acquire_at(current_tick, tokens)
    }

    #[inline(always)]
    fn try_acquire_verbose(&self, tokens: Uint) -> RateLimitResult {
        let elapsed = self.time_source.now();
        let current_tick = P::to_ticks(elapsed);
        self.core.try_acquire_verbose_at(current_tick, tokens)
            .map_err(|e| RateLimitError::from_core_error(e, |ticks| P::from_ticks(ticks)))
    }

    #[inline(always)]
    fn capacity_remaining(&self) -> Result<Uint, SimpleRateLimitError> {
        let elapsed = self.time_source.now();
        let current_tick = P::to_ticks(elapsed);
        self.core.capacity_remaining(current_tick)
    }
}

impl ApproximateSlidingWindowBuilder {
    /// Creates a new builder for configuring an ApproximateSlidingWindow.
    ///
    /// This is the preferred way to start building an ApproximateSlidingWindow.
    ///
    /// # Returns
    /// A new ApproximateSlidingWindowBuilder ready for configuration
    /// 
    /// # Examples
    ///
    /// ```rust
    /// use rate_guard::{Millis, MockTimeSource, RateLimit};
    /// use rate_guard::limits::ApproximateSlidingWindowBuilder;
    /// use std::time::Duration;
    ///
    /// let window = ApproximateSlidingWindowBuilder::builder()
    ///     .capacity(100)
    ///     .window_duration(Duration::from_secs(60))
    ///     .with_time(MockTimeSource::new())
    ///     .with_precision::<Millis>()
    ///     .build()
    ///     .unwrap();
    ///
    /// assert!(window.try_acquire(50).is_ok());
    /// ```
    pub fn builder() -> Self {
        Self {
            capacity: None,
            window_duration: None,
        }
    }

    /// Sets the maximum capacity of the approximate sliding window.
    ///
    /// The capacity determines the maximum number of actions allowed within
    /// the sliding window duration.
    ///
    /// # Arguments
    /// * `capacity` - Maximum number of actions allowed in the sliding window
    ///
    /// # Returns
    /// Self for method chaining
    pub fn capacity(mut self, capacity: Uint) -> Self {
        self.capacity = Some(capacity);
        self
    }

    /// Sets the duration of the sliding window.
    ///
    /// This determines the total time span over which the rate limiting
    /// is applied. The algorithm provides an approximation of this sliding window.
    ///
    /// # Arguments
    /// * `duration` - Duration of the sliding window
    ///
    /// # Returns
    /// Self for method chaining
    pub fn window_duration(mut self, duration: Duration) -> Self {
        self.window_duration = Some(duration);
        self
    }

    /// Configures the time source for the approximate sliding window.
    ///
    /// This method sets the time source and returns a builder that can
    /// then configure the precision type.
    ///
    /// # Arguments
    /// * `time_source` - The time source instance to use
    ///
    /// # Returns
    /// A builder with time source configured
    pub fn with_time<T: TimeSource>(self, time_source: T) -> ApproximateSlidingWindowBuilderWithTime<T> {
        ApproximateSlidingWindowBuilderWithTime {
            capacity: self.capacity,
            window_duration: self.window_duration,
            time_source,
        }
    }
}

impl Default for ApproximateSlidingWindowBuilder {
    fn default() -> Self {
        Self::builder()
    }
}

impl<T: TimeSource> ApproximateSlidingWindowBuilderWithTime<T> {
    /// Sets the precision type for time calculations.
    ///
    /// This method configures the precision type and returns a fully
    /// configured builder ready to build the ApproximateSlidingWindow.
    ///
    /// # Type Parameters
    /// * `P` - The precision type to use for time calculations
    ///
    /// # Returns
    /// A fully configured builder ready to build the ApproximateSlidingWindow
    pub fn with_precision<P: Precision>(self) -> ConfiguredApproximateSlidingWindowBuilder<P, T> {
        ConfiguredApproximateSlidingWindowBuilder {
            capacity: self.capacity,
            window_duration: self.window_duration,
            time_source: self.time_source,
            _precision: PhantomData,
        }
    }
}

impl<P: Precision, T: TimeSource> ConfiguredApproximateSlidingWindowBuilder<P, T> {
    /// Builds the ApproximateSlidingWindow with the configured parameters.
    ///
    /// All required configuration must be provided before calling this method.
    /// This method also validates that all parameter values are valid.
    ///
    /// # Returns
    /// * `Ok(ApproximateSlidingWindow)` - Successfully created approximate sliding window
    /// * `Err(BuildError)` - Missing required configuration or invalid parameter values
    ///
    /// # Errors
    ///
    /// Returns `BuildError` if:
    /// - Any required configuration is missing
    /// - Any parameter has an invalid value (e.g., capacity = 0)
    pub fn build(self) -> BuildResult<ApproximateSlidingWindow<P, T>> {
        let capacity = self.capacity.ok_or(BuildError::MissingArgument("capacity"))?;
        let window_duration = self.window_duration.ok_or(BuildError::MissingArgument("window_duration"))?;

        // Validate parameter values
        if capacity == 0 {
            return Err(BuildError::InvalidArgument {
                field: "capacity",
                reason: "must be greater than 0"
            });
        }
        if window_duration == Duration::ZERO {
            return Err(BuildError::InvalidArgument {
                field: "window_duration",
                reason: "must be greater than zero"
            });
        }

        let window_ticks = P::to_ticks(window_duration);
        let core = ApproximateSlidingWindowCore::new(capacity, window_ticks);

        Ok(ApproximateSlidingWindow {
            core,
            time_source: self.time_source,
            _precision: PhantomData,
        })
    }
}

// Type alias for backward compatibility
pub use ApproximateSlidingWindow as PrecisionApproximateSlidingWindow;