rate_guard/limits/
token_bucket.rs

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