1use std::time::Duration;
7
8use crate::{
9 FixedWindowRateLimiter, LeakyBucketLimiter, SlidingWindowLogLimiter, SlidingWindowRateLimiter,
10 TokenBucketRateLimiter,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
15pub enum LimitAlgorithm {
16 FixedWindow,
18 SlidingWindow,
20 SlidingWindowLog,
22 TokenBucket,
24 LeakyBucket,
26}
27
28#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
32pub struct RateLimitConfig {
33 pub algorithm: LimitAlgorithm,
35 pub capacity: u64,
37 pub rate: f64,
39 pub window_ms: u64,
41 pub max_keys: usize,
43}
44
45impl Default for RateLimitConfig {
46 fn default() -> Self {
47 Self {
48 algorithm: LimitAlgorithm::TokenBucket,
49 capacity: 100,
50 rate: 10.0,
51 window_ms: 1000,
52 max_keys: crate::DEFAULT_MAX_KEYS,
53 }
54 }
55}
56
57impl RateLimitConfig {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn from_json_str(s: &str) -> Result<Self, ConfigError> {
65 serde_json::from_str(s).map_err(|e| ConfigError::Parse(e.to_string()))
66 }
67
68 pub fn to_json_string(&self) -> Result<String, ConfigError> {
70 serde_json::to_string(self).map_err(|e| ConfigError::Serialize(e.to_string()))
71 }
72
73 pub fn validate(&self) -> Result<(), ConfigError> {
75 if self.capacity == 0 {
76 return Err(ConfigError::InvalidCapacity);
77 }
78 match self.algorithm {
79 LimitAlgorithm::TokenBucket | LimitAlgorithm::LeakyBucket => {
80 if self.rate < 0.0 {
81 return Err(ConfigError::InvalidRate);
82 }
83 }
84 _ => {}
85 }
86 match self.algorithm {
87 LimitAlgorithm::FixedWindow
88 | LimitAlgorithm::SlidingWindow
89 | LimitAlgorithm::SlidingWindowLog => {
90 if self.window_ms == 0 {
91 return Err(ConfigError::InvalidWindow);
92 }
93 }
94 _ => {}
95 }
96 if self.max_keys == 0 {
97 return Err(ConfigError::InvalidMaxKeys);
98 }
99 Ok(())
100 }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
105pub enum ConfigError {
106 Parse(String),
107 Serialize(String),
108 InvalidCapacity,
109 InvalidRate,
110 InvalidWindow,
111 InvalidMaxKeys,
112}
113
114impl std::fmt::Display for ConfigError {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 match self {
117 ConfigError::Parse(msg) => write!(f, "config parse error: {}", msg),
118 ConfigError::Serialize(msg) => write!(f, "config serialize error: {}", msg),
119 ConfigError::InvalidCapacity => write!(f, "capacity must be positive"),
120 ConfigError::InvalidRate => write!(f, "rate must be non-negative"),
121 ConfigError::InvalidWindow => write!(f, "window size must be positive"),
122 ConfigError::InvalidMaxKeys => write!(f, "max_keys must be positive"),
123 }
124 }
125}
126
127impl std::error::Error for ConfigError {}
128
129pub struct RateLimitConfigBuilder {
146 config: RateLimitConfig,
147}
148
149impl Default for RateLimitConfigBuilder {
150 fn default() -> Self {
151 Self::new()
152 }
153}
154
155impl RateLimitConfigBuilder {
156 pub fn new() -> Self {
158 Self {
159 config: RateLimitConfig::default(),
160 }
161 }
162
163 pub fn algorithm(mut self, algo: LimitAlgorithm) -> Self {
165 self.config.algorithm = algo;
166 self
167 }
168
169 pub fn capacity(mut self, cap: u64) -> Self {
171 self.config.capacity = cap;
172 self
173 }
174
175 pub fn rate(mut self, rate: f64) -> Self {
177 self.config.rate = rate;
178 self
179 }
180
181 pub fn window_ms(mut self, ms: u64) -> Self {
183 self.config.window_ms = ms;
184 self
185 }
186
187 pub fn window_secs(mut self, secs: u64) -> Self {
189 self.config.window_ms = secs * 1000;
190 self
191 }
192
193 pub fn max_keys(mut self, max: usize) -> Self {
195 self.config.max_keys = max;
196 self
197 }
198
199 pub fn build(self) -> RateLimitConfig {
201 self.config
202 }
203
204 pub fn build_checked(self) -> Result<RateLimitConfig, ConfigError> {
206 self.config.validate()?;
207 Ok(self.config)
208 }
209
210 pub fn build_token_bucket(&self) -> TokenBucketRateLimiter {
212 TokenBucketRateLimiter::new(self.config.capacity, self.config.rate)
213 .with_max_keys(self.config.max_keys)
214 }
215
216 pub fn build_sliding_window(&self) -> SlidingWindowRateLimiter {
218 SlidingWindowRateLimiter::new(
219 self.config.capacity,
220 Duration::from_millis(self.config.window_ms),
221 )
222 .with_max_keys(self.config.max_keys)
223 }
224
225 pub fn build_fixed_window(&self) -> FixedWindowRateLimiter {
227 FixedWindowRateLimiter::new(
228 self.config.capacity,
229 Duration::from_millis(self.config.window_ms),
230 )
231 .with_max_keys(self.config.max_keys)
232 }
233
234 pub fn build_leaky_bucket(&self) -> LeakyBucketLimiter {
236 LeakyBucketLimiter::new(self.config.capacity, self.config.rate)
237 .with_max_keys(self.config.max_keys)
238 }
239
240 pub fn build_sliding_window_log(&self) -> SlidingWindowLogLimiter {
242 SlidingWindowLogLimiter::new(
243 self.config.capacity,
244 Duration::from_millis(self.config.window_ms),
245 )
246 .with_max_keys(self.config.max_keys)
247 }
248}
249
250#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
254pub struct TieredRateLimitConfig {
255 pub ip: RateLimitConfig,
257 pub user: RateLimitConfig,
259 pub api: RateLimitConfig,
261 pub global: RateLimitConfig,
263}
264
265impl Default for TieredRateLimitConfig {
266 fn default() -> Self {
267 Self {
268 ip: RateLimitConfig {
269 algorithm: LimitAlgorithm::SlidingWindow,
270 capacity: 1000,
271 rate: 0.0,
272 window_ms: 60_000,
273 max_keys: 10_000,
274 },
275 user: RateLimitConfig {
276 algorithm: LimitAlgorithm::TokenBucket,
277 capacity: 100,
278 rate: 10.0,
279 window_ms: 1000,
280 max_keys: 10_000,
281 },
282 api: RateLimitConfig {
283 algorithm: LimitAlgorithm::FixedWindow,
284 capacity: 500,
285 rate: 0.0,
286 window_ms: 60_000,
287 max_keys: 1000,
288 },
289 global: RateLimitConfig {
290 algorithm: LimitAlgorithm::TokenBucket,
291 capacity: 10_000,
292 rate: 100.0,
293 window_ms: 1000,
294 max_keys: 1,
295 },
296 }
297 }
298}
299
300impl TieredRateLimitConfig {
301 pub fn new() -> Self {
303 Self::default()
304 }
305
306 pub fn validate(&self) -> Result<(), ConfigError> {
308 self.ip.validate()?;
309 self.user.validate()?;
310 self.api.validate()?;
311 self.global.validate()?;
312 Ok(())
313 }
314
315 pub fn from_json_str(s: &str) -> Result<Self, ConfigError> {
317 serde_json::from_str(s).map_err(|e| ConfigError::Parse(e.to_string()))
318 }
319
320 pub fn to_json_string(&self) -> Result<String, ConfigError> {
322 serde_json::to_string(self).map_err(|e| ConfigError::Serialize(e.to_string()))
323 }
324}
325
326pub struct TieredConfigBuilder {
328 config: TieredRateLimitConfig,
329}
330
331impl Default for TieredConfigBuilder {
332 fn default() -> Self {
333 Self::new()
334 }
335}
336
337impl TieredConfigBuilder {
338 pub fn new() -> Self {
340 Self {
341 config: TieredRateLimitConfig::default(),
342 }
343 }
344
345 pub fn ip(mut self, config: RateLimitConfig) -> Self {
347 self.config.ip = config;
348 self
349 }
350
351 pub fn user(mut self, config: RateLimitConfig) -> Self {
353 self.config.user = config;
354 self
355 }
356
357 pub fn api(mut self, config: RateLimitConfig) -> Self {
359 self.config.api = config;
360 self
361 }
362
363 pub fn global(mut self, config: RateLimitConfig) -> Self {
365 self.config.global = config;
366 self
367 }
368
369 pub fn build(self) -> TieredRateLimitConfig {
371 self.config
372 }
373
374 pub fn build_checked(self) -> Result<TieredRateLimitConfig, ConfigError> {
376 self.config.validate()?;
377 Ok(self.config)
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384 use crate::RateLimiter;
385
386 #[test]
387 fn test_limit_algorithm_serde() {
388 let algo = LimitAlgorithm::TokenBucket;
389 let json = serde_json::to_string(&algo).unwrap();
390 let back: LimitAlgorithm = serde_json::from_str(&json).unwrap();
391 assert_eq!(algo, back);
392 }
393
394 #[test]
395 fn test_rate_limit_config_default() {
396 let config = RateLimitConfig::default();
397 assert_eq!(config.algorithm, LimitAlgorithm::TokenBucket);
398 assert_eq!(config.capacity, 100);
399 assert_eq!(config.rate, 10.0);
400 }
401
402 #[test]
403 fn test_rate_limit_config_validate_ok() {
404 let config = RateLimitConfig {
405 algorithm: LimitAlgorithm::TokenBucket,
406 capacity: 100,
407 rate: 10.0,
408 window_ms: 1000,
409 max_keys: 1000,
410 };
411 assert!(config.validate().is_ok());
412 }
413
414 #[test]
415 fn test_rate_limit_config_validate_zero_capacity() {
416 let config = RateLimitConfig {
417 capacity: 0,
418 ..Default::default()
419 };
420 assert_eq!(config.validate(), Err(ConfigError::InvalidCapacity));
421 }
422
423 #[test]
424 fn test_rate_limit_config_validate_negative_rate() {
425 let config = RateLimitConfig {
426 algorithm: LimitAlgorithm::TokenBucket,
427 rate: -1.0,
428 ..Default::default()
429 };
430 assert_eq!(config.validate(), Err(ConfigError::InvalidRate));
431 }
432
433 #[test]
434 fn test_rate_limit_config_validate_zero_window() {
435 let config = RateLimitConfig {
436 algorithm: LimitAlgorithm::FixedWindow,
437 window_ms: 0,
438 ..Default::default()
439 };
440 assert_eq!(config.validate(), Err(ConfigError::InvalidWindow));
441 }
442
443 #[test]
444 fn test_rate_limit_config_validate_zero_max_keys() {
445 let config = RateLimitConfig {
446 max_keys: 0,
447 ..Default::default()
448 };
449 assert_eq!(config.validate(), Err(ConfigError::InvalidMaxKeys));
450 }
451
452 #[test]
453 fn test_rate_limit_config_json_roundtrip() {
454 let config = RateLimitConfig::default();
455 let json = config.to_json_string().unwrap();
456 let back = RateLimitConfig::from_json_str(&json).unwrap();
457 assert_eq!(config.algorithm, back.algorithm);
458 assert_eq!(config.capacity, back.capacity);
459 }
460
461 #[test]
462 fn test_config_builder_basic() {
463 let config = RateLimitConfigBuilder::new()
464 .algorithm(LimitAlgorithm::SlidingWindow)
465 .capacity(200)
466 .window_secs(60)
467 .max_keys(5000)
468 .build();
469 assert_eq!(config.algorithm, LimitAlgorithm::SlidingWindow);
470 assert_eq!(config.capacity, 200);
471 assert_eq!(config.window_ms, 60_000);
472 assert_eq!(config.max_keys, 5000);
473 }
474
475 #[test]
476 fn test_config_builder_checked_ok() {
477 let config = RateLimitConfigBuilder::new()
478 .algorithm(LimitAlgorithm::TokenBucket)
479 .capacity(100)
480 .rate(10.0)
481 .build_checked();
482 assert!(config.is_ok());
483 }
484
485 #[test]
486 fn test_config_builder_checked_fail() {
487 let config = RateLimitConfigBuilder::new().capacity(0).build_checked();
488 assert!(config.is_err());
489 }
490
491 #[test]
492 fn test_config_builder_build_token_bucket() {
493 let builder = RateLimitConfigBuilder::new()
494 .algorithm(LimitAlgorithm::TokenBucket)
495 .capacity(10)
496 .rate(1.0)
497 .max_keys(100);
498 let limiter = builder.build_token_bucket();
499 assert_eq!(limiter.capacity(), 10);
500 let r = limiter.acquire("k").unwrap();
501 assert!(r.allowed);
502 }
503
504 #[test]
505 fn test_config_builder_build_sliding_window() {
506 let builder = RateLimitConfigBuilder::new()
507 .algorithm(LimitAlgorithm::SlidingWindow)
508 .capacity(10)
509 .window_secs(60);
510 let limiter = builder.build_sliding_window();
511 assert_eq!(limiter.max_requests(), 10);
512 let r = limiter.acquire("k").unwrap();
513 assert!(r.allowed);
514 }
515
516 #[test]
517 fn test_config_builder_build_fixed_window() {
518 let builder = RateLimitConfigBuilder::new()
519 .algorithm(LimitAlgorithm::FixedWindow)
520 .capacity(10)
521 .window_secs(60);
522 let limiter = builder.build_fixed_window();
523 assert_eq!(limiter.max_requests(), 10);
524 let r = limiter.acquire("k").unwrap();
525 assert!(r.allowed);
526 }
527
528 #[test]
529 fn test_config_builder_build_leaky_bucket() {
530 let builder = RateLimitConfigBuilder::new()
531 .algorithm(LimitAlgorithm::LeakyBucket)
532 .capacity(10)
533 .rate(1.0);
534 let limiter = builder.build_leaky_bucket();
535 assert_eq!(limiter.capacity(), 10);
536 let r = limiter.acquire("k").unwrap();
537 assert!(r.allowed);
538 }
539
540 #[test]
541 fn test_config_builder_build_sliding_window_log() {
542 let builder = RateLimitConfigBuilder::new()
543 .algorithm(LimitAlgorithm::SlidingWindowLog)
544 .capacity(10)
545 .window_secs(60);
546 let limiter = builder.build_sliding_window_log();
547 assert_eq!(limiter.max_requests(), 10);
548 let r = limiter.acquire("k").unwrap();
549 assert!(r.allowed);
550 }
551
552 #[test]
553 fn test_tiered_config_default() {
554 let config = TieredRateLimitConfig::default();
555 assert_eq!(config.ip.capacity, 1000);
556 assert_eq!(config.user.capacity, 100);
557 assert_eq!(config.api.capacity, 500);
558 assert_eq!(config.global.capacity, 10_000);
559 }
560
561 #[test]
562 fn test_tiered_config_validate() {
563 let config = TieredRateLimitConfig::default();
564 assert!(config.validate().is_ok());
565 }
566
567 #[test]
568 fn test_tiered_config_json_roundtrip() {
569 let config = TieredRateLimitConfig::default();
570 let json = config.to_json_string().unwrap();
571 let back = TieredRateLimitConfig::from_json_str(&json).unwrap();
572 assert_eq!(back.ip.capacity, config.ip.capacity);
573 }
574
575 #[test]
576 fn test_tiered_config_builder() {
577 let config = TieredConfigBuilder::new()
578 .ip(RateLimitConfig {
579 capacity: 2000,
580 ..Default::default()
581 })
582 .user(RateLimitConfig {
583 capacity: 200,
584 ..Default::default()
585 })
586 .build();
587 assert_eq!(config.ip.capacity, 2000);
588 assert_eq!(config.user.capacity, 200);
589 }
590
591 #[test]
592 fn test_tiered_config_builder_checked() {
593 let config = TieredConfigBuilder::new().build_checked();
594 assert!(config.is_ok());
595 }
596
597 #[test]
598 fn test_config_builder_window_ms() {
599 let config = RateLimitConfigBuilder::new().window_ms(500).build();
600 assert_eq!(config.window_ms, 500);
601 }
602
603 #[test]
604 fn test_config_builder_rate() {
605 let config = RateLimitConfigBuilder::new().rate(5.0).build();
606 assert_eq!(config.rate, 5.0);
607 }
608}