rate-guard 0.1.0

Thread-safe rate limiting library with multiple algorithms and Duration-based configuration
Documentation
//! Sliding window counter rate limiter implementation with integrated time source and Builder pattern.
//!
//! This module provides a sliding window counter rate limiter that uses multiple
//! buckets to implement precise 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::SlidingWindowCounterBuilder;
//! use std::time::Duration;
//! 
//! let counter = SlidingWindowCounterBuilder::builder()
//!     .capacity(100)
//!     .bucket_duration(Duration::from_secs(10))
//!     .bucket_count(6)
//!     .with_time(MockTimeSource::new())
//!     .with_precision::<Millis>()
//!     .build()
//!     .unwrap();
//!
//! assert!(counter.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::SlidingWindowCounterCore;
use rate_guard_core::SimpleRateLimitError;

/// Sliding window counter rate limiter with integrated time source and precision handling.
///
/// The sliding window counter algorithm maintains multiple time buckets to provide
/// precise sliding window behavior. As time progresses, old buckets are discarded
/// and new buckets are created, maintaining a sliding window of the specified duration.
///
/// # 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 SlidingWindowCounter<P: Precision, T: TimeSource> {
    /// The underlying core implementation that handles the sliding window counter logic.
    core: SlidingWindowCounterCore,
    /// 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 SlidingWindowCounter instances.
///
/// SlidingWindowCounterBuilder provides a fluent interface for configuring all aspects
/// of a sliding window counter rate limiter. The precision and time source types are
/// determined through the builder chain.
#[derive(Debug)]
pub struct SlidingWindowCounterBuilder {
    /// Maximum number of tokens allowed in the sliding window.
    capacity: Option<Uint>,
    /// Duration of each bucket in the sliding window.
    bucket_duration: Option<Duration>,
    /// Total number of buckets in the sliding window.
    bucket_count: Option<Uint>,
}

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

/// Fully configured builder for SlidingWindowCounter with precision and time source set.
///
/// This builder is created after calling `with_precision()` and is ready to build
/// the final SlidingWindowCounter instance.
pub struct ConfiguredSlidingWindowCounterBuilder<P: Precision, T: TimeSource> {
    /// Maximum number of tokens allowed in the sliding window.
    capacity: Option<Uint>,
    /// Duration of each bucket in the sliding window.
    bucket_duration: Option<Duration>,
    /// Total number of buckets in the sliding window.
    bucket_count: Option<Uint>,
    /// 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 SlidingWindowCounter<P, T>
where
    T: std::fmt::Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SlidingWindowCounter")
            .field("time_source", &self.time_source)
            .field("_precision", &std::any::type_name::<P>())
            .finish_non_exhaustive()
    }
}

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

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

impl<P: Precision, T: TimeSource> RateLimit for SlidingWindowCounter<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 SlidingWindowCounterBuilder {
    /// Creates a new builder for configuring a SlidingWindowCounter.
    ///
    /// This is the preferred way to start building a SlidingWindowCounter.
    ///
    /// # Returns
    /// A new SlidingWindowCounterBuilder ready for configuration
    /// 
    /// # Examples
    ///
    /// ```rust
    /// use rate_guard::{Millis, MockTimeSource, RateLimit};
    /// use rate_guard::limits::SlidingWindowCounterBuilder;
    /// use std::time::Duration;
    ///
    /// let counter = SlidingWindowCounterBuilder::builder()
    ///     .capacity(100)
    ///     .bucket_duration(Duration::from_secs(10))
    ///     .bucket_count(6)
    ///     .with_time(MockTimeSource::new())
    ///     .with_precision::<Millis>()
    ///     .build()
    ///     .unwrap();
    ///
    /// assert!(counter.try_acquire(50).is_ok());
    /// ```
    pub fn builder() -> Self {
        Self {
            capacity: None,
            bucket_duration: None,
            bucket_count: None,
        }
    }

    /// Sets the maximum capacity of the sliding window counter.
    ///
    /// The capacity determines the maximum number of tokens allowed across
    /// all buckets in the sliding window.
    ///
    /// # Arguments
    /// * `capacity` - Maximum number of tokens 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 each bucket in the sliding window.
    ///
    /// This determines the granularity of the sliding window. Smaller bucket
    /// durations provide more precise sliding behavior but use more memory.
    ///
    /// # Arguments
    /// * `duration` - Duration of each bucket
    ///
    /// # Returns
    /// Self for method chaining
    pub fn bucket_duration(mut self, duration: Duration) -> Self {
        self.bucket_duration = Some(duration);
        self
    }

    /// Sets the number of buckets in the sliding window.
    ///
    /// The total window duration is `bucket_duration * bucket_count`.
    /// More buckets provide more precise sliding behavior but use more memory.
    ///
    /// # Arguments
    /// * `count` - Number of buckets in the sliding window
    ///
    /// # Returns
    /// Self for method chaining
    pub fn bucket_count(mut self, count: Uint) -> Self {
        self.bucket_count = Some(count);
        self
    }

    /// Configures the time source for the sliding window counter.
    ///
    /// 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) -> SlidingWindowCounterBuilderWithTime<T> {
        SlidingWindowCounterBuilderWithTime {
            capacity: self.capacity,
            bucket_duration: self.bucket_duration,
            bucket_count: self.bucket_count,
            time_source,
        }
    }
}

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

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

impl<P: Precision, T: TimeSource> ConfiguredSlidingWindowCounterBuilder<P, T> {
    /// Builds the SlidingWindowCounter 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(SlidingWindowCounter)` - Successfully created sliding window counter
    /// * `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<SlidingWindowCounter<P, T>> {
        let capacity = self.capacity.ok_or(BuildError::MissingArgument("capacity"))?;
        let bucket_duration = self.bucket_duration.ok_or(BuildError::MissingArgument("bucket_duration"))?;
        let bucket_count = self.bucket_count.ok_or(BuildError::MissingArgument("bucket_count"))?;

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

        let bucket_ticks = P::to_ticks(bucket_duration);
        let core = SlidingWindowCounterCore::new(capacity, bucket_ticks, bucket_count);

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

// Type alias for backward compatibility
pub use SlidingWindowCounter as PrecisionSlidingWindowCounter;