rate_guard/limits/
sliding_window_counter.rs

1//! Sliding window counter rate limiter implementation with integrated time source and Builder pattern.
2//!
3//! This module provides a sliding window counter rate limiter that uses multiple
4//! buckets to implement precise sliding windows. The implementation integrates time source
5//! management directly and uses a Builder pattern for configuration.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use rate_guard::{Millis, MockTimeSource, RateLimit};
11//! use rate_guard::limits::SlidingWindowCounterBuilder;
12//! use std::time::Duration;
13//! 
14//! let counter = SlidingWindowCounterBuilder::builder()
15//!     .capacity(100)
16//!     .bucket_duration(Duration::from_secs(10))
17//!     .bucket_count(6)
18//!     .with_time(MockTimeSource::new())
19//!     .with_precision::<Millis>()
20//!     .build()
21//!     .unwrap();
22//!
23//! assert!(counter.try_acquire(50).is_ok());
24//! ```
25
26use std::time::Duration;
27use std::marker::PhantomData;
28use crate::limits::RateLimit;
29use crate::{Uint, SimpleRateLimitResult, RateLimitResult, BuildResult};
30use crate::precision::Precision;
31use crate::time_source::TimeSource;
32use crate::error::{RateLimitError, BuildError};
33use rate_guard_core::cores::SlidingWindowCounterCore;
34use rate_guard_core::SimpleRateLimitError;
35
36/// Sliding window counter rate limiter with integrated time source and precision handling.
37///
38/// The sliding window counter algorithm maintains multiple time buckets to provide
39/// precise sliding window behavior. As time progresses, old buckets are discarded
40/// and new buckets are created, maintaining a sliding window of the specified duration.
41///
42/// # Type Parameters
43/// * `P` - The precision type that determines how Duration values are converted to internal ticks
44/// * `T` - The time source type that provides elapsed time since the rate limiter's reference point
45pub struct SlidingWindowCounter<P: Precision, T: TimeSource> {
46    /// The underlying core implementation that handles the sliding window counter logic.
47    core: SlidingWindowCounterCore,
48    /// The time source for getting current elapsed time.
49    time_source: T,
50    /// Phantom data to associate the Precision type with this instance.
51    _precision: PhantomData<P>,
52}
53
54/// Builder for configuring and creating SlidingWindowCounter instances.
55///
56/// SlidingWindowCounterBuilder provides a fluent interface for configuring all aspects
57/// of a sliding window counter rate limiter. The precision and time source types are
58/// determined through the builder chain.
59#[derive(Debug)]
60pub struct SlidingWindowCounterBuilder {
61    /// Maximum number of tokens allowed in the sliding window.
62    capacity: Option<Uint>,
63    /// Duration of each bucket in the sliding window.
64    bucket_duration: Option<Duration>,
65    /// Total number of buckets in the sliding window.
66    bucket_count: Option<Uint>,
67}
68
69/// Builder with time source configured.
70///
71/// This intermediate builder is created after calling `with_time()` and allows
72/// setting the precision type.
73pub struct SlidingWindowCounterBuilderWithTime<T: TimeSource> {
74    /// Maximum number of tokens allowed in the sliding window.
75    capacity: Option<Uint>,
76    /// Duration of each bucket in the sliding window.
77    bucket_duration: Option<Duration>,
78    /// Total number of buckets in the sliding window.
79    bucket_count: Option<Uint>,
80    /// Time source instance for elapsed time management.
81    time_source: T,
82}
83
84/// Fully configured builder for SlidingWindowCounter with precision and time source set.
85///
86/// This builder is created after calling `with_precision()` and is ready to build
87/// the final SlidingWindowCounter instance.
88pub struct ConfiguredSlidingWindowCounterBuilder<P: Precision, T: TimeSource> {
89    /// Maximum number of tokens allowed in the sliding window.
90    capacity: Option<Uint>,
91    /// Duration of each bucket in the sliding window.
92    bucket_duration: Option<Duration>,
93    /// Total number of buckets in the sliding window.
94    bucket_count: Option<Uint>,
95    /// Time source instance for elapsed time management.
96    time_source: T,
97    /// Phantom data to associate the Precision type with this instance.
98    _precision: PhantomData<P>,
99}
100
101impl<P: Precision, T: TimeSource> std::fmt::Debug for SlidingWindowCounter<P, T>
102where
103    T: std::fmt::Debug,
104{
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        f.debug_struct("SlidingWindowCounter")
107            .field("time_source", &self.time_source)
108            .field("_precision", &std::any::type_name::<P>())
109            .finish_non_exhaustive()
110    }
111}
112
113impl<T: TimeSource> std::fmt::Debug for SlidingWindowCounterBuilderWithTime<T>
114where
115    T: std::fmt::Debug,
116{
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        f.debug_struct("SlidingWindowCounterBuilderWithTime")
119            .field("capacity", &self.capacity)
120            .field("bucket_duration", &self.bucket_duration)
121            .field("bucket_count", &self.bucket_count)
122            .field("time_source", &self.time_source)
123            .finish()
124    }
125}
126
127impl<P: Precision, T: TimeSource> std::fmt::Debug for ConfiguredSlidingWindowCounterBuilder<P, T>
128where
129    T: std::fmt::Debug,
130{
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.debug_struct("ConfiguredSlidingWindowCounterBuilder")
133            .field("capacity", &self.capacity)
134            .field("bucket_duration", &self.bucket_duration)
135            .field("bucket_count", &self.bucket_count)
136            .field("time_source", &self.time_source)
137            .field("_precision", &std::any::type_name::<P>())
138            .finish()
139    }
140}
141
142impl<P: Precision, T: TimeSource> RateLimit for SlidingWindowCounter<P, T> {
143    #[inline(always)]
144    fn try_acquire(&self, tokens: Uint) -> SimpleRateLimitResult {
145        let elapsed = self.time_source.now();
146        let current_tick = P::to_ticks(elapsed);
147        self.core.try_acquire_at(current_tick, tokens)
148    }
149
150    #[inline(always)]
151    fn try_acquire_verbose(&self, tokens: Uint) -> RateLimitResult {
152        let elapsed = self.time_source.now();
153        let current_tick = P::to_ticks(elapsed);
154        self.core.try_acquire_verbose_at(current_tick, tokens)
155            .map_err(|e| RateLimitError::from_core_error(e, |ticks| P::from_ticks(ticks)))
156    }
157
158    #[inline(always)]
159    fn capacity_remaining(&self) -> Result<Uint, SimpleRateLimitError> {
160        let elapsed = self.time_source.now();
161        let current_tick = P::to_ticks(elapsed);
162        self.core.capacity_remaining(current_tick)
163    }
164}
165
166impl SlidingWindowCounterBuilder {
167    /// Creates a new builder for configuring a SlidingWindowCounter.
168    ///
169    /// This is the preferred way to start building a SlidingWindowCounter.
170    ///
171    /// # Returns
172    /// A new SlidingWindowCounterBuilder ready for configuration
173    /// 
174    /// # Examples
175    ///
176    /// ```rust
177    /// use rate_guard::{Millis, MockTimeSource, RateLimit};
178    /// use rate_guard::limits::SlidingWindowCounterBuilder;
179    /// use std::time::Duration;
180    ///
181    /// let counter = SlidingWindowCounterBuilder::builder()
182    ///     .capacity(100)
183    ///     .bucket_duration(Duration::from_secs(10))
184    ///     .bucket_count(6)
185    ///     .with_time(MockTimeSource::new())
186    ///     .with_precision::<Millis>()
187    ///     .build()
188    ///     .unwrap();
189    ///
190    /// assert!(counter.try_acquire(50).is_ok());
191    /// ```
192    pub fn builder() -> Self {
193        Self {
194            capacity: None,
195            bucket_duration: None,
196            bucket_count: None,
197        }
198    }
199
200    /// Sets the maximum capacity of the sliding window counter.
201    ///
202    /// The capacity determines the maximum number of tokens allowed across
203    /// all buckets in the sliding window.
204    ///
205    /// # Arguments
206    /// * `capacity` - Maximum number of tokens allowed in the sliding window
207    ///
208    /// # Returns
209    /// Self for method chaining
210    pub fn capacity(mut self, capacity: Uint) -> Self {
211        self.capacity = Some(capacity);
212        self
213    }
214
215    /// Sets the duration of each bucket in the sliding window.
216    ///
217    /// This determines the granularity of the sliding window. Smaller bucket
218    /// durations provide more precise sliding behavior but use more memory.
219    ///
220    /// # Arguments
221    /// * `duration` - Duration of each bucket
222    ///
223    /// # Returns
224    /// Self for method chaining
225    pub fn bucket_duration(mut self, duration: Duration) -> Self {
226        self.bucket_duration = Some(duration);
227        self
228    }
229
230    /// Sets the number of buckets in the sliding window.
231    ///
232    /// The total window duration is `bucket_duration * bucket_count`.
233    /// More buckets provide more precise sliding behavior but use more memory.
234    ///
235    /// # Arguments
236    /// * `count` - Number of buckets in the sliding window
237    ///
238    /// # Returns
239    /// Self for method chaining
240    pub fn bucket_count(mut self, count: Uint) -> Self {
241        self.bucket_count = Some(count);
242        self
243    }
244
245    /// Configures the time source for the sliding window counter.
246    ///
247    /// This method sets the time source and returns a builder that can
248    /// then configure the precision type.
249    ///
250    /// # Arguments
251    /// * `time_source` - The time source instance to use
252    ///
253    /// # Returns
254    /// A builder with time source configured
255    pub fn with_time<T: TimeSource>(self, time_source: T) -> SlidingWindowCounterBuilderWithTime<T> {
256        SlidingWindowCounterBuilderWithTime {
257            capacity: self.capacity,
258            bucket_duration: self.bucket_duration,
259            bucket_count: self.bucket_count,
260            time_source,
261        }
262    }
263}
264
265impl Default for SlidingWindowCounterBuilder {
266    fn default() -> Self {
267        Self::builder()
268    }
269}
270
271impl<T: TimeSource> SlidingWindowCounterBuilderWithTime<T> {
272    /// Sets the precision type for time calculations.
273    ///
274    /// This method configures the precision type and returns a fully
275    /// configured builder ready to build the SlidingWindowCounter.
276    ///
277    /// # Type Parameters
278    /// * `P` - The precision type to use for time calculations
279    ///
280    /// # Returns
281    /// A fully configured builder ready to build the SlidingWindowCounter
282    pub fn with_precision<P: Precision>(self) -> ConfiguredSlidingWindowCounterBuilder<P, T> {
283        ConfiguredSlidingWindowCounterBuilder {
284            capacity: self.capacity,
285            bucket_duration: self.bucket_duration,
286            bucket_count: self.bucket_count,
287            time_source: self.time_source,
288            _precision: PhantomData,
289        }
290    }
291}
292
293impl<P: Precision, T: TimeSource> ConfiguredSlidingWindowCounterBuilder<P, T> {
294    /// Builds the SlidingWindowCounter with the configured parameters.
295    ///
296    /// All required configuration must be provided before calling this method.
297    /// This method also validates that all parameter values are valid.
298    ///
299    /// # Returns
300    /// * `Ok(SlidingWindowCounter)` - Successfully created sliding window counter
301    /// * `Err(BuildError)` - Missing required configuration or invalid parameter values
302    ///
303    /// # Errors
304    ///
305    /// Returns `BuildError` if:
306    /// - Any required configuration is missing
307    /// - Any parameter has an invalid value (e.g., capacity = 0)
308    pub fn build(self) -> BuildResult<SlidingWindowCounter<P, T>> {
309        let capacity = self.capacity.ok_or(BuildError::MissingArgument("capacity"))?;
310        let bucket_duration = self.bucket_duration.ok_or(BuildError::MissingArgument("bucket_duration"))?;
311        let bucket_count = self.bucket_count.ok_or(BuildError::MissingArgument("bucket_count"))?;
312
313        // Validate parameter values
314        if capacity == 0 {
315            return Err(BuildError::InvalidArgument {
316                field: "capacity",
317                reason: "must be greater than 0"
318            });
319        }
320        if bucket_duration == Duration::ZERO {
321            return Err(BuildError::InvalidArgument {
322                field: "bucket_duration",
323                reason: "must be greater than zero"
324            });
325        }
326        if bucket_count == 0 {
327            return Err(BuildError::InvalidArgument {
328                field: "bucket_count",
329                reason: "must be greater than 0"
330            });
331        }
332
333        let bucket_ticks = P::to_ticks(bucket_duration);
334        let core = SlidingWindowCounterCore::new(capacity, bucket_ticks, bucket_count);
335
336        Ok(SlidingWindowCounter {
337            core,
338            time_source: self.time_source,
339            _precision: PhantomData,
340        })
341    }
342}
343
344// Type alias for backward compatibility
345pub use SlidingWindowCounter as PrecisionSlidingWindowCounter;