tokio_rate_limit/algorithm/mod.rs
1//! Rate limiting algorithms.
2
3use crate::error::Result;
4use crate::limiter::RateLimitDecision;
5use async_trait::async_trait;
6
7mod leaky_bucket;
8mod token_bucket;
9
10// v0.6.0 experimental optimizations
11mod cached_token_bucket;
12mod simd_token_bucket;
13mod zerocopy_token_bucket;
14
15// v0.7.0 probabilistic rate limiting
16mod probabilistic_token_bucket;
17
18pub use leaky_bucket::LeakyBucket;
19pub use token_bucket::TokenBucket;
20
21// Experimental exports (v0.6.0)
22pub use cached_token_bucket::CachedTokenBucket;
23pub use simd_token_bucket::SimdTokenBucket;
24pub use zerocopy_token_bucket::ZeroCopyTokenBucket;
25
26// Probabilistic exports (v0.7.0)
27pub use probabilistic_token_bucket::ProbabilisticTokenBucket;
28
29/// Private module for the sealed trait pattern.
30///
31/// This prevents external implementations of the Algorithm trait while maintaining
32/// flexibility for internal algorithm implementations. This allows us to make
33/// breaking changes to the trait in the future without requiring a semver major bump.
34mod private {
35 pub trait Sealed {}
36}
37
38/// Trait for rate limiting algorithms.
39///
40/// Implementations of this trait define how rate limiting decisions are made.
41/// The trait is async to allow for potential I/O operations in custom implementations.
42///
43/// # Sealed Trait
44///
45/// This trait is sealed and cannot be implemented outside of this crate. This design
46/// allows us to add new methods or change the trait in minor version updates without
47/// breaking semver guarantees. If you need a custom algorithm, please open an issue
48/// to discuss adding it to the library.
49///
50/// # Available Algorithms
51///
52/// - [`TokenBucket`] - Allows bursts up to capacity, refills at constant rate (zero-copy optimized in v0.4.0)
53/// - [`LeakyBucket`] - Enforces steady rate, smooths traffic (v0.3.0)
54/// - [`CachedTokenBucket`] - Thread-local cached token bucket for hot-key workloads (v0.4.0)
55///
56/// # Experimental Algorithms (Not Recommended for Production)
57///
58/// - [`ZeroCopyTokenBucket`] - Zero-copy prototype (integrated into TokenBucket in v0.4.0)
59/// - [`SimdTokenBucket`] - SIMD prototype (deferred, no performance benefit)
60///
61/// # Future Algorithms
62///
63/// - Sliding Window - More precise rate limiting
64#[async_trait]
65pub trait Algorithm: Send + Sync + private::Sealed {
66 /// Checks if a request for the given key should be permitted.
67 ///
68 /// # Arguments
69 ///
70 /// * `key` - A string identifier for the client/resource being rate limited
71 ///
72 /// # Returns
73 ///
74 /// A `RateLimitDecision` indicating whether the request is permitted and
75 /// additional metadata about the rate limit status.
76 async fn check(&self, key: &str) -> Result<RateLimitDecision>;
77
78 /// Checks if a request with the given cost should be permitted.
79 ///
80 /// Cost represents the number of tokens to consume. The default implementation
81 /// uses a cost of 1 (equivalent to `check()`), but algorithms can override this
82 /// to support weighted rate limiting.
83 ///
84 /// # Arguments
85 ///
86 /// * `key` - A string identifier for the client/resource being rate limited
87 /// * `cost` - Number of tokens to consume (must be > 0)
88 ///
89 /// # Returns
90 ///
91 /// A `RateLimitDecision` indicating whether the request is permitted and
92 /// additional metadata about the rate limit status.
93 ///
94 /// # Default Behavior
95 ///
96 /// The default implementation rejects requests with cost > 1 if there aren't
97 /// enough remaining tokens. Algorithms should override this for proper
98 /// weighted rate limiting support.
99 async fn check_with_cost(&self, key: &str, cost: u64) -> Result<RateLimitDecision> {
100 if cost == 1 {
101 self.check(key).await
102 } else {
103 // Default: check if we have enough tokens for the cost
104 let mut decision = self.check(key).await?;
105 if cost > 1 && decision.remaining.unwrap_or(0) < cost {
106 decision.permitted = false;
107 }
108 Ok(decision)
109 }
110 }
111}