rate-guard 0.1.0

Thread-safe rate limiting library with multiple algorithms and Duration-based configuration
Documentation
//! Fixed window counter rate limiter implementation with integrated time source and Builder pattern.
//!
//! This module provides a fixed window counter rate limiter that uses fixed
//! time windows with capacity limits. 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::FixedWindowCounterBuilder;
//! use std::time::Duration;
//!
//! let counter = FixedWindowCounterBuilder::builder()
//!     .capacity(100)
//!     .window_duration(Duration::from_secs(60))
//!     .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::FixedWindowCounterCore;
use rate_guard_core::SimpleRateLimitError;

/// Fixed window counter rate limiter with integrated time source and precision handling.
///
/// The fixed window counter algorithm divides time into fixed-size windows and
/// tracks the number of requests within each window. When a window expires,
/// the counter resets to zero, allowing new requests up to the capacity limit.
///
/// # 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 FixedWindowCounter<P: Precision, T: TimeSource> {
    /// The underlying core implementation that handles the fixed window counter logic.
    core: FixedWindowCounterCore,
    /// 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 FixedWindowCounter instances.
///
/// FixedWindowCounterBuilder provides a fluent interface for configuring all aspects
/// of a fixed window counter rate limiter. The precision and time source types are
/// determined through the builder chain.
#[derive(Debug)]
pub struct FixedWindowCounterBuilder {
    /// Maximum number of actions allowed per window.
    capacity: Option<Uint>,
    /// Duration that defines the fixed window length.
    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 FixedWindowCounterBuilderWithTime<T: TimeSource> {
    /// Maximum number of actions allowed per window.
    capacity: Option<Uint>,
    /// Duration that defines the fixed window length.
    window_duration: Option<Duration>,
    /// Time source instance for elapsed time management.
    time_source: T,
}

/// Fully configured builder for FixedWindowCounter with precision and time source set.
///
/// This builder is created after calling `with_precision()` and is ready to build
/// the final FixedWindowCounter instance.
pub struct ConfiguredFixedWindowCounterBuilder<P: Precision, T: TimeSource> {
    /// Maximum number of actions allowed per window.
    capacity: Option<Uint>,
    /// Duration that defines the fixed window length.
    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 FixedWindowCounter<P, T>
where
    T: std::fmt::Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FixedWindowCounter")
            .field("time_source", &self.time_source)
            .field("_precision", &std::any::type_name::<P>())
            .finish_non_exhaustive()
    }
}

impl<T: TimeSource> std::fmt::Debug for FixedWindowCounterBuilderWithTime<T>
where
    T: std::fmt::Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FixedWindowCounterBuilderWithTime")
            .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 ConfiguredFixedWindowCounterBuilder<P, T>
where
    T: std::fmt::Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ConfiguredFixedWindowCounterBuilder")
            .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 FixedWindowCounter<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 FixedWindowCounterBuilder {
    /// Creates a new builder for configuring a FixedWindowCounter.
    ///
    /// This is the preferred way to start building a FixedWindowCounter.
    ///
    /// # Returns
    /// A new FixedWindowCounterBuilder ready for configuration
    /// 
    /// # Examples
    ///
    /// ```rust
    /// use rate_guard::{Millis, MockTimeSource, RateLimit};
    /// use rate_guard::limits::FixedWindowCounterBuilder;
    /// use std::time::Duration;
    ///
    /// let counter = FixedWindowCounterBuilder::builder()
    ///     .capacity(100)
    ///     .window_duration(Duration::from_secs(60))
    ///     .with_time(MockTimeSource::new())
    ///     .with_precision::<Millis>()
    ///     .build()
    ///     .unwrap();
    ///
    /// assert!(counter.try_acquire(50).is_ok());
    /// ```
    pub fn builder() -> Self {
        Self {
            capacity: None,
            window_duration: None,
        }
    }

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

    /// Sets the duration of each fixed window.
    ///
    /// This determines how long each window lasts before resetting.
    /// Common values include minutes or hours for rate limiting APIs.
    ///
    /// # Arguments
    /// * `duration` - Duration of each fixed 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 fixed 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) -> FixedWindowCounterBuilderWithTime<T> {
        FixedWindowCounterBuilderWithTime {
            capacity: self.capacity,
            window_duration: self.window_duration,
            time_source,
        }
    }
}

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

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

impl<P: Precision, T: TimeSource> ConfiguredFixedWindowCounterBuilder<P, T> {
    /// Builds the FixedWindowCounter 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(FixedWindowCounter)` - Successfully created fixed 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<FixedWindowCounter<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_size_ticks = P::to_ticks(window_duration);
        let core = FixedWindowCounterCore::new(capacity, window_size_ticks);

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

// Type alias for backward compatibility
pub use FixedWindowCounter as PrecisionFixedWindowCounter;