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;