1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3mod backend;
38mod error;
39mod metrics;
40mod quota;
41
42#[cfg(feature = "redis")]
43mod redis;
44
45#[cfg(feature = "sqlite")]
46mod sqlite;
47
48#[cfg(feature = "sliding-window")]
49mod sliding_window;
50
51#[cfg(feature = "tower")]
52pub mod client_ip;
53
54#[cfg(feature = "tower")]
55mod tower_layer;
56
57#[cfg(feature = "tower")]
58pub use client_ip::{
59 ClientIpConfig, ClientIpError, ClientIpSource, IpNet, MissingClientIdentity,
60 MissingClientPolicy, ResolvedClient,
61};
62#[cfg(feature = "tower")]
63pub use tower_layer::{KeyExtractor, RateLimitLayer, RateLimitService};
64
65pub use backend::RateLimitBackend;
66#[cfg(feature = "in-memory")]
67pub use backend::InMemoryBackend;
68
69pub use error::RateLimitError;
70pub use metrics::RateLimitResult;
71pub use quota::Quota;
72#[cfg(feature = "sliding-window")]
73pub use sliding_window::SlidingWindowBackend;
74
75#[cfg(feature = "redis")]
76pub use redis::RedisBackend;
77
78#[cfg(feature = "sqlite")]
79pub use sqlite::SqliteBackend;
80
81use std::sync::Arc;
82
83#[derive(Clone)]
85pub struct RateLimiter<B: RateLimitBackend> {
86 quota: Quota,
87 backend: Arc<B>,
88}
89
90impl<B: RateLimitBackend> RateLimiter<B> {
91 pub fn new(quota: Quota, backend: B) -> Self {
93 Self {
94 quota,
95 backend: Arc::new(backend),
96 }
97 }
98
99 pub async fn check(&self, key: &str) -> RateLimitResult {
101 self.backend.check(key, &self.quota).await
102 }
103
104 pub fn check_sync(&self, key: &str) -> RateLimitResult {
109 #[allow(clippy::expect_used)]
113 let rt = tokio::runtime::Builder::new_current_thread()
114 .build()
115 .expect("failed to create tokio runtime for sync check");
116 rt.block_on(self.check(key))
117 }
118}
119
120#[cfg(feature = "in-memory")]
121pub use keyed::KeyedRateLimiter;
122
123#[cfg(feature = "in-memory")]
124mod keyed {
125 use dashmap::DashMap;
126
127 use crate::RateLimiter;
128 use crate::backend::RateLimitBackend;
129 use crate::metrics::RateLimitResult;
130 use crate::quota::Quota;
131
132 pub struct KeyedRateLimiter<B: RateLimitBackend + Clone> {
156 limiters: DashMap<String, RateLimiter<B>>,
157 default_quota: Quota,
158 backend: std::sync::Arc<B>,
162 }
163
164 impl<B: RateLimitBackend + Clone> KeyedRateLimiter<B> {
165 pub fn new(default_quota: Quota, backend: B) -> Self {
167 Self {
168 limiters: DashMap::new(),
169 default_quota,
170 backend: std::sync::Arc::new(backend),
171 }
172 }
173
174 pub fn with_quota_for_key(&self, key: &str, quota: Quota) {
180 self.limiters.insert(
181 key.to_string(),
182 RateLimiter {
183 quota,
184 backend: std::sync::Arc::clone(&self.backend),
185 },
186 );
187 }
188
189 pub async fn check(&self, key: &str) -> RateLimitResult {
191 let limiter = self
192 .limiters
193 .entry(key.to_string())
194 .or_insert_with(|| RateLimiter {
195 quota: self.default_quota.clone(),
196 backend: std::sync::Arc::clone(&self.backend),
197 })
198 .value()
199 .clone();
200 limiter.check(key).await
201 }
202
203 pub fn check_sync(&self, key: &str) -> RateLimitResult {
208 #[allow(clippy::expect_used)]
212 let rt = tokio::runtime::Builder::new_current_thread()
213 .build()
214 .expect("failed to create tokio runtime for sync check");
215 rt.block_on(self.check(key))
216 }
217 }
218}
219
220#[allow(
224 clippy::unwrap_used,
225 clippy::expect_used,
226 clippy::indexing_slicing,
227 clippy::panic
228)]
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use std::time::Duration;
233
234 #[test]
235 fn quota_per_second() {
236 let q = Quota::per_second(10);
237 assert_eq!(q.burst, 10);
238 assert_eq!(q.interval(), Duration::from_millis(100));
239 }
240
241 #[test]
242 fn quota_per_minute() {
243 let q = Quota::per_minute(100);
244 assert_eq!(q.burst, 100);
245 assert_eq!(q.interval(), Duration::from_millis(600));
246 }
247
248 #[test]
249 fn quota_per_hour() {
250 let q = Quota::per_hour(3600);
251 assert_eq!(q.burst, 3600);
252 assert_eq!(q.interval(), Duration::from_secs(1));
253 }
254
255 #[test]
256 fn quota_allow_burst() {
257 let q = Quota::per_second(10).allow_burst(20);
258 assert_eq!(q.burst, 20);
259 }
260
261 #[test]
262 fn rate_limit_result_fields() {
263 let r = RateLimitResult {
264 allowed: true,
265 remaining: 5,
266 reset_at: std::time::Instant::now(),
267 limit: 10,
268 retry_after: None,
269 };
270 assert!(r.allowed);
271 assert_eq!(r.remaining, 5);
272 assert_eq!(r.limit, 10);
273 assert!(r.retry_after.is_none());
274 }
275
276 #[test]
277 fn rate_limit_result_headers() {
278 let r = RateLimitResult {
279 allowed: true,
280 remaining: 7,
281 reset_at: std::time::Instant::now() + Duration::from_secs(30),
282 limit: 10,
283 retry_after: None,
284 };
285 let headers = r.headers();
286 assert_eq!(headers.len(), 3);
287 assert_eq!(headers[0].0, "X-RateLimit-Limit");
288 assert_eq!(headers[0].1, "10");
289 assert_eq!(headers[1].0, "X-RateLimit-Remaining");
290 assert_eq!(headers[1].1, "7");
291 assert_eq!(headers[2].0, "X-RateLimit-Reset");
292 }
293
294 #[test]
295 fn rate_limit_result_retry_after() {
296 let r = RateLimitResult {
297 allowed: false,
298 remaining: 0,
299 reset_at: std::time::Instant::now(),
300 limit: 10,
301 retry_after: Some(Duration::from_millis(100)),
302 };
303 assert!(!r.allowed);
304 assert_eq!(r.retry_after, Some(Duration::from_millis(100)));
305 }
306
307 #[test]
308 fn in_memory_backend_new() {
309 let _ = InMemoryBackend::new();
310 }
311
312 #[test]
313 fn in_memory_backend_default() {
314 let _ = InMemoryBackend::default();
315 }
316
317 #[test]
318 fn rate_limit_error_display() {
319 let e = RateLimitError::RateLimited;
320 assert_eq!(e.to_string(), "rate limit exceeded");
321
322 let e = RateLimitError::BackendError("connection refused".into());
323 assert_eq!(
324 e.to_string(),
325 "rate limit backend error: connection refused"
326 );
327 }
328
329 #[tokio::test]
330 async fn first_request_is_allowed() {
331 let backend = InMemoryBackend::new();
332 let limiter = RateLimiter::new(Quota::per_second(10), backend);
333 let result = limiter.check("user-1").await;
334 assert!(result.allowed);
335 assert_eq!(result.remaining, 9);
336 assert_eq!(result.limit, 10);
337 assert!(result.retry_after.is_none());
338 }
339
340 #[tokio::test]
341 async fn rate_limit_exhaustion() {
342 let backend = InMemoryBackend::new();
343 let limiter = RateLimiter::new(Quota::per_second(2), backend);
344 let r1 = limiter.check("user-1").await;
345 assert!(r1.allowed);
346 let r2 = limiter.check("user-1").await;
347 assert!(r2.allowed);
348 let r3 = limiter.check("user-1").await;
349 assert!(!r3.allowed);
350 assert!(r3.retry_after.is_some());
351 }
352
353 #[tokio::test]
354 async fn keyed_limiter_default_quota() {
355 use crate::KeyedRateLimiter;
356 let backend = InMemoryBackend::new();
357 let limiter = KeyedRateLimiter::new(Quota::per_second(5), backend);
358
359 let r = limiter.check("client-1").await;
360 assert!(r.allowed);
361 assert_eq!(r.limit, 5);
362 }
363
364 #[tokio::test]
365 async fn keyed_limiter_custom_quota_per_key() {
366 use crate::KeyedRateLimiter;
367 let backend = InMemoryBackend::new();
368 let limiter = KeyedRateLimiter::new(Quota::per_second(100), backend);
369
370 limiter.with_quota_for_key("login", Quota::per_second(2));
372
373 let r_login = limiter.check("login").await;
374 assert!(r_login.allowed);
375 assert_eq!(r_login.limit, 2);
376
377 let r_api = limiter.check("api").await;
378 assert!(r_api.allowed);
379 assert_eq!(r_api.limit, 100);
380 }
381
382 #[tokio::test]
383 async fn keyed_limiter_isolation() {
384 use crate::KeyedRateLimiter;
385 let backend = InMemoryBackend::new();
386 let limiter = KeyedRateLimiter::new(Quota::per_second(1), backend);
387
388 let r1 = limiter.check("a").await;
389 assert!(r1.allowed);
390
391 let r1_again = limiter.check("a").await;
393 assert!(!r1_again.allowed);
394
395 let r2 = limiter.check("b").await;
396 assert!(r2.allowed);
397 }
398
399 #[test]
400 fn keyed_limiter_sync_check() {
401 use crate::KeyedRateLimiter;
402 let backend = InMemoryBackend::new();
403 let limiter = KeyedRateLimiter::new(Quota::per_second(10), backend);
404
405 let r = limiter.check_sync("client-1");
406 assert!(r.allowed);
407 assert_eq!(r.limit, 10);
408 }
409
410 #[tokio::test]
411 async fn keyed_limiter_headers() {
412 use crate::KeyedRateLimiter;
413 let backend = InMemoryBackend::new();
414 let limiter = KeyedRateLimiter::new(Quota::per_second(10), backend);
415
416 let r = limiter.check("client-1").await;
417 let headers = r.headers();
418 assert!(headers.iter().any(|(k, _)| *k == "X-RateLimit-Limit"));
419 assert!(
420 headers
421 .iter()
422 .any(|(k, v)| *k == "X-RateLimit-Remaining" && v == "9")
423 );
424 }
425
426 #[test]
427 fn sync_check_basic() {
428 let backend = InMemoryBackend::new();
429 let limiter = RateLimiter::new(Quota::per_second(10), backend);
430 let r = limiter.check_sync("user-1");
431 assert!(r.allowed);
432 }
433}