rate_guard/limits/fixed_window_counter.rs
1//! Fixed window counter rate limiter implementation with integrated time source and Builder pattern.
2//!
3//! This module provides a fixed window counter rate limiter that uses fixed
4//! time windows with capacity limits. 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::FixedWindowCounterBuilder;
12//! use std::time::Duration;
13//!
14//! let counter = FixedWindowCounterBuilder::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!(counter.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::FixedWindowCounterCore;
33use rate_guard_core::SimpleRateLimitError;
34
35/// Fixed window counter rate limiter with integrated time source and precision handling.
36///
37/// The fixed window counter algorithm divides time into fixed-size windows and
38/// tracks the number of requests within each window. When a window expires,
39/// the counter resets to zero, allowing new requests up to the capacity limit.
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 FixedWindowCounter<P: Precision, T: TimeSource> {
45 /// The underlying core implementation that handles the fixed window counter logic.
46 core: FixedWindowCounterCore,
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 FixedWindowCounter instances.
54///
55/// FixedWindowCounterBuilder provides a fluent interface for configuring all aspects
56/// of a fixed window counter rate limiter. The precision and time source types are
57/// determined through the builder chain.
58#[derive(Debug)]
59pub struct FixedWindowCounterBuilder {
60 /// Maximum number of actions allowed per window.
61 capacity: Option<Uint>,
62 /// Duration that defines the fixed window length.
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 FixedWindowCounterBuilderWithTime<T: TimeSource> {
71 /// Maximum number of actions allowed per window.
72 capacity: Option<Uint>,
73 /// Duration that defines the fixed window length.
74 window_duration: Option<Duration>,
75 /// Time source instance for elapsed time management.
76 time_source: T,
77}
78
79/// Fully configured builder for FixedWindowCounter with precision and time source set.
80///
81/// This builder is created after calling `with_precision()` and is ready to build
82/// the final FixedWindowCounter instance.
83pub struct ConfiguredFixedWindowCounterBuilder<P: Precision, T: TimeSource> {
84 /// Maximum number of actions allowed per window.
85 capacity: Option<Uint>,
86 /// Duration that defines the fixed window length.
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 FixedWindowCounter<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("FixedWindowCounter")
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 FixedWindowCounterBuilderWithTime<T>
107where
108 T: std::fmt::Debug,
109{
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.debug_struct("FixedWindowCounterBuilderWithTime")
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 ConfiguredFixedWindowCounterBuilder<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("ConfiguredFixedWindowCounterBuilder")
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 FixedWindowCounter<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 FixedWindowCounterBuilder {
158 /// Creates a new builder for configuring a FixedWindowCounter.
159 ///
160 /// This is the preferred way to start building a FixedWindowCounter.
161 ///
162 /// # Returns
163 /// A new FixedWindowCounterBuilder ready for configuration
164 ///
165 /// # Examples
166 ///
167 /// ```rust
168 /// use rate_guard::{Millis, MockTimeSource, RateLimit};
169 /// use rate_guard::limits::FixedWindowCounterBuilder;
170 /// use std::time::Duration;
171 ///
172 /// let counter = FixedWindowCounterBuilder::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!(counter.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 fixed window counter.
190 ///
191 /// The capacity determines the maximum number of actions allowed within
192 /// each fixed window.
193 ///
194 /// # Arguments
195 /// * `capacity` - Maximum number of actions allowed per 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 each fixed window.
205 ///
206 /// This determines how long each window lasts before resetting.
207 /// Common values include minutes or hours for rate limiting APIs.
208 ///
209 /// # Arguments
210 /// * `duration` - Duration of each fixed 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 fixed window counter.
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) -> FixedWindowCounterBuilderWithTime<T> {
230 FixedWindowCounterBuilderWithTime {
231 capacity: self.capacity,
232 window_duration: self.window_duration,
233 time_source,
234 }
235 }
236}
237
238impl Default for FixedWindowCounterBuilder {
239 fn default() -> Self {
240 Self::builder()
241 }
242}
243
244impl<T: TimeSource> FixedWindowCounterBuilderWithTime<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 FixedWindowCounter.
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 FixedWindowCounter
255 pub fn with_precision<P: Precision>(self) -> ConfiguredFixedWindowCounterBuilder<P, T> {
256 ConfiguredFixedWindowCounterBuilder {
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> ConfiguredFixedWindowCounterBuilder<P, T> {
266 /// Builds the FixedWindowCounter 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(FixedWindowCounter)` - Successfully created fixed window counter
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<FixedWindowCounter<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_size_ticks = P::to_ticks(window_duration);
299 let core = FixedWindowCounterCore::new(capacity, window_size_ticks);
300
301 Ok(FixedWindowCounter {
302 core,
303 time_source: self.time_source,
304 _precision: PhantomData,
305 })
306 }
307}
308
309// Type alias for backward compatibility
310pub use FixedWindowCounter as PrecisionFixedWindowCounter;