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}