rate_guard/limits/
approximate_sliding_window.rs

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