1use std::collections::HashMap;
35use std::sync::{Arc, RwLock};
36use std::time::Duration;
37
38use serde::{Deserialize, Serialize};
39
40use crate::rate_limit::{create_rate_limiter, RateLimitConfig, RateLimiter};
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct TenantRateLimit {
51 pub config: RateLimitConfig,
53 pub quota: Option<u64>,
55}
56
57impl TenantRateLimit {
58 #[must_use]
60 pub fn new(config: RateLimitConfig) -> Self {
61 Self {
62 config,
63 quota: None,
64 }
65 }
66
67 #[must_use]
69 pub fn with_quota(mut self, quota: u64) -> Self {
70 self.quota = Some(quota);
71 self
72 }
73}
74
75impl From<RateLimitConfig> for TenantRateLimit {
76 fn from(config: RateLimitConfig) -> Self {
77 Self::new(config)
78 }
79}
80
81impl Default for TenantRateLimit {
82 fn default() -> Self {
83 Self::new(RateLimitConfig::default())
84 }
85}
86
87struct TenantState {
89 limiter: Box<dyn RateLimiter>,
91 quota: Option<u64>,
93 granted: u64,
95 denied: u64,
97}
98
99impl TenantState {
100 fn from_policy(policy: &TenantRateLimit) -> Self {
101 Self {
102 limiter: create_rate_limiter(policy.config.clone()),
103 quota: policy.quota,
104 granted: 0,
105 denied: 0,
106 }
107 }
108
109 fn quota_exhausted(&self) -> bool {
111 matches!(self.quota, Some(limit) if self.granted >= limit)
112 }
113
114 fn try_acquire(&mut self) -> bool {
115 if self.quota_exhausted() {
116 self.denied += 1;
117 return false;
118 }
119 if self.limiter.try_acquire() {
120 self.granted += 1;
121 true
122 } else {
123 self.denied += 1;
124 false
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
131pub struct TenantUsage {
132 pub granted: u64,
134 pub denied: u64,
136 pub quota: Option<u64>,
138 pub quota_remaining: Option<u64>,
140}
141
142struct TenantRegistry {
144 default_policy: Option<TenantRateLimit>,
146 overrides: HashMap<String, TenantRateLimit>,
148 tenants: HashMap<String, TenantState>,
150}
151
152impl TenantRegistry {
153 fn new(default_policy: Option<TenantRateLimit>) -> Self {
154 Self {
155 default_policy,
156 overrides: HashMap::new(),
157 tenants: HashMap::new(),
158 }
159 }
160
161 fn policy_for(&self, tenant_id: &str) -> Option<TenantRateLimit> {
163 self.overrides
164 .get(tenant_id)
165 .cloned()
166 .or_else(|| self.default_policy.clone())
167 }
168
169 fn try_acquire(&mut self, tenant_id: &str) -> bool {
171 if let Some(state) = self.tenants.get_mut(tenant_id) {
172 return state.try_acquire();
173 }
174 let Some(policy) = self.policy_for(tenant_id) else {
176 return true;
178 };
179 let mut state = TenantState::from_policy(&policy);
180 let granted = state.try_acquire();
181 self.tenants.insert(tenant_id.to_string(), state);
182 granted
183 }
184}
185
186#[derive(Clone)]
193pub struct TenantRateLimiter {
194 inner: Arc<RwLock<TenantRegistry>>,
195}
196
197impl std::fmt::Debug for TenantRateLimiter {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 let mut dbg = f.debug_struct("TenantRateLimiter");
200 if let Ok(guard) = self.inner.read() {
201 dbg.field("tracked_tenants", &guard.tenants.len())
202 .field("overrides", &guard.overrides.len())
203 .field("has_default", &guard.default_policy.is_some());
204 }
205 dbg.finish()
206 }
207}
208
209impl TenantRateLimiter {
210 #[must_use]
213 pub fn new() -> Self {
214 Self {
215 inner: Arc::new(RwLock::new(TenantRegistry::new(None))),
216 }
217 }
218
219 #[must_use]
222 pub fn with_default(config: RateLimitConfig) -> Self {
223 Self {
224 inner: Arc::new(RwLock::new(TenantRegistry::new(Some(
225 TenantRateLimit::new(config),
226 )))),
227 }
228 }
229
230 #[must_use]
233 pub fn with_default_policy(policy: TenantRateLimit) -> Self {
234 Self {
235 inner: Arc::new(RwLock::new(TenantRegistry::new(Some(policy)))),
236 }
237 }
238
239 pub fn set_tenant_config(&self, tenant_id: impl Into<String>, config: RateLimitConfig) {
243 self.set_tenant_policy(tenant_id, TenantRateLimit::new(config));
244 }
245
246 pub fn set_tenant_policy(&self, tenant_id: impl Into<String>, policy: TenantRateLimit) {
251 let id = tenant_id.into();
252 if let Ok(mut guard) = self.inner.write() {
253 guard.tenants.remove(&id);
254 guard.overrides.insert(id, policy);
255 }
256 }
257
258 pub fn set_tenant_quota(&self, tenant_id: impl Into<String>, quota: Option<u64>) {
264 let id = tenant_id.into();
265 if let Ok(mut guard) = self.inner.write() {
266 let base = guard
267 .policy_for(&id)
268 .unwrap_or_else(|| TenantRateLimit::new(RateLimitConfig::default()));
269 let policy = TenantRateLimit {
270 config: base.config,
271 quota,
272 };
273 guard.tenants.remove(&id);
274 guard.overrides.insert(id, policy);
275 }
276 }
277
278 pub fn remove_tenant(&self, tenant_id: &str) {
281 if let Ok(mut guard) = self.inner.write() {
282 guard.overrides.remove(tenant_id);
283 guard.tenants.remove(tenant_id);
284 }
285 }
286
287 #[must_use]
293 pub fn try_acquire(&self, tenant_id: &str) -> bool {
294 if let Ok(mut guard) = self.inner.write() {
295 guard.try_acquire(tenant_id)
296 } else {
297 true
299 }
300 }
301
302 #[must_use]
305 pub fn has_capacity(&self, tenant_id: &str) -> bool {
306 if let Ok(guard) = self.inner.read() {
307 if let Some(state) = guard.tenants.get(tenant_id) {
308 return !state.quota_exhausted() && state.limiter.available_permits() > 0;
309 }
310 return match guard.policy_for(tenant_id) {
312 Some(policy) => policy.config.effective_burst() > 0,
313 None => true,
314 };
315 }
316 true
317 }
318
319 #[must_use]
322 pub fn time_until_available(&self, tenant_id: &str) -> Duration {
323 if let Ok(guard) = self.inner.read() {
324 if let Some(state) = guard.tenants.get(tenant_id) {
325 if state.quota_exhausted() {
326 return Duration::MAX;
328 }
329 return state.limiter.time_until_available();
330 }
331 }
332 Duration::ZERO
333 }
334
335 #[must_use]
337 pub fn has_rate_limit(&self, tenant_id: &str) -> bool {
338 if let Ok(guard) = self.inner.read() {
339 guard.overrides.contains_key(tenant_id) || guard.default_policy.is_some()
340 } else {
341 false
342 }
343 }
344
345 #[must_use]
347 pub fn policy_for(&self, tenant_id: &str) -> Option<TenantRateLimit> {
348 self.inner.read().ok().and_then(|g| g.policy_for(tenant_id))
349 }
350
351 #[must_use]
353 pub fn usage(&self, tenant_id: &str) -> Option<TenantUsage> {
354 let guard = self.inner.read().ok()?;
355 let state = guard.tenants.get(tenant_id)?;
356 let quota_remaining = state.quota.map(|q| q.saturating_sub(state.granted));
357 Some(TenantUsage {
358 granted: state.granted,
359 denied: state.denied,
360 quota: state.quota,
361 quota_remaining,
362 })
363 }
364
365 pub fn reset_tenant(&self, tenant_id: &str) {
368 if let Ok(mut guard) = self.inner.write() {
369 if let Some(state) = guard.tenants.get_mut(tenant_id) {
370 state.limiter.reset();
371 state.granted = 0;
372 state.denied = 0;
373 }
374 }
375 }
376
377 pub fn reset_all(&self) {
379 if let Ok(mut guard) = self.inner.write() {
380 for state in guard.tenants.values_mut() {
381 state.limiter.reset();
382 state.granted = 0;
383 state.denied = 0;
384 }
385 }
386 }
387
388 #[must_use]
390 pub fn tracked_count(&self) -> usize {
391 self.inner.read().map(|g| g.tenants.len()).unwrap_or(0)
392 }
393}
394
395impl Default for TenantRateLimiter {
396 fn default() -> Self {
397 Self::new()
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn no_default_allows_unconfigured_tenants() {
407 let limiter = TenantRateLimiter::new();
408 for _ in 0..1000 {
409 assert!(limiter.try_acquire("anyone"));
410 }
411 assert!(!limiter.has_rate_limit("anyone"));
412 }
413
414 #[test]
415 fn per_tenant_isolation() {
416 let limiter = TenantRateLimiter::new();
417 limiter.set_tenant_config("alice", RateLimitConfig::new(1000.0).with_burst(2));
419 limiter.set_tenant_config("bob", RateLimitConfig::new(1000.0).with_burst(2));
420
421 assert!(limiter.try_acquire("alice"));
423 assert!(limiter.try_acquire("alice"));
424 assert!(!limiter.try_acquire("alice"));
425
426 assert!(limiter.try_acquire("bob"));
428 assert!(limiter.try_acquire("bob"));
429 assert!(!limiter.try_acquire("bob"));
430 }
431
432 #[test]
433 fn default_vs_override() {
434 let limiter = TenantRateLimiter::with_default(RateLimitConfig::new(1000.0).with_burst(100));
436 limiter.set_tenant_config("trial", RateLimitConfig::new(1000.0).with_burst(1));
437
438 assert!(limiter.try_acquire("trial"));
440 assert!(!limiter.try_acquire("trial"));
441
442 for _ in 0..100 {
444 assert!(limiter.try_acquire("default_tenant"));
445 }
446 assert!(!limiter.try_acquire("default_tenant"));
447 }
448
449 #[test]
450 fn default_applies_to_all_unknown_tenants() {
451 let limiter = TenantRateLimiter::with_default(RateLimitConfig::new(1000.0).with_burst(2));
452 assert!(limiter.has_rate_limit("whoever"));
453 assert!(limiter.try_acquire("t1"));
454 assert!(limiter.try_acquire("t1"));
455 assert!(!limiter.try_acquire("t1"));
456 assert!(limiter.try_acquire("t2"));
458 }
459
460 #[test]
461 fn quota_caps_total_permits() {
462 let limiter = TenantRateLimiter::new();
463 limiter.set_tenant_policy(
465 "metered",
466 TenantRateLimit::new(RateLimitConfig::new(1_000_000.0).with_burst(1000)).with_quota(3),
467 );
468
469 assert!(limiter.try_acquire("metered"));
470 assert!(limiter.try_acquire("metered"));
471 assert!(limiter.try_acquire("metered"));
472 assert!(!limiter.try_acquire("metered"));
474 assert!(!limiter.try_acquire("metered"));
475
476 let usage = limiter.usage("metered").expect("tenant materialized");
477 assert_eq!(usage.granted, 3);
478 assert_eq!(usage.denied, 2);
479 assert_eq!(usage.quota, Some(3));
480 assert_eq!(usage.quota_remaining, Some(0));
481 }
482
483 #[test]
484 fn quota_exhaustion_does_not_affect_other_tenant() {
485 let limiter = TenantRateLimiter::new();
486 limiter.set_tenant_policy(
487 "capped",
488 TenantRateLimit::new(RateLimitConfig::new(1_000_000.0).with_burst(1000)).with_quota(1),
489 );
490 limiter.set_tenant_config("uncapped", RateLimitConfig::new(1000.0).with_burst(5));
491
492 assert!(limiter.try_acquire("capped"));
493 assert!(!limiter.try_acquire("capped")); for _ in 0..5 {
497 assert!(limiter.try_acquire("uncapped"));
498 }
499 }
500
501 #[test]
502 fn set_tenant_quota_preserves_rate() {
503 let limiter =
504 TenantRateLimiter::with_default(RateLimitConfig::new(1_000_000.0).with_burst(10));
505 limiter.set_tenant_quota("vip", Some(2));
506 let policy = limiter.policy_for("vip").expect("override created");
507 assert_eq!(policy.quota, Some(2));
508 assert_eq!(policy.config.effective_burst(), 10);
510
511 assert!(limiter.try_acquire("vip"));
512 assert!(limiter.try_acquire("vip"));
513 assert!(!limiter.try_acquire("vip"));
514
515 limiter.set_tenant_quota("vip", None);
517 assert!(limiter.policy_for("vip").expect("override").quota.is_none());
518 for _ in 0..10 {
519 assert!(limiter.try_acquire("vip"));
520 }
521 }
522
523 #[test]
524 fn reset_tenant_restores_capacity() {
525 let limiter = TenantRateLimiter::new();
526 limiter.set_tenant_config("svc", RateLimitConfig::new(0.0001).with_burst(2));
527 assert!(limiter.try_acquire("svc"));
528 assert!(limiter.try_acquire("svc"));
529 assert!(!limiter.try_acquire("svc"));
530
531 limiter.reset_tenant("svc");
532 assert!(limiter.try_acquire("svc"));
533
534 let usage = limiter.usage("svc").expect("materialized");
535 assert_eq!(usage.granted, 1);
537 }
538
539 #[test]
540 fn reset_all_restores_all_tenants() {
541 let limiter = TenantRateLimiter::new();
542 limiter.set_tenant_config("a", RateLimitConfig::new(0.0001).with_burst(1));
543 limiter.set_tenant_config("b", RateLimitConfig::new(0.0001).with_burst(1));
544 assert!(limiter.try_acquire("a"));
545 assert!(limiter.try_acquire("b"));
546 assert!(!limiter.try_acquire("a"));
547 assert!(!limiter.try_acquire("b"));
548
549 limiter.reset_all();
550 assert!(limiter.try_acquire("a"));
551 assert!(limiter.try_acquire("b"));
552 }
553
554 #[test]
555 fn remove_tenant_falls_back_to_default() {
556 let limiter = TenantRateLimiter::with_default(RateLimitConfig::new(1000.0).with_burst(5));
557 limiter.set_tenant_config("temp", RateLimitConfig::new(1000.0).with_burst(1));
558 assert!(limiter.try_acquire("temp"));
559 assert!(!limiter.try_acquire("temp")); limiter.remove_tenant("temp");
562 let policy = limiter.policy_for("temp").expect("default applies");
564 assert_eq!(policy.config.effective_burst(), 5);
565 for _ in 0..5 {
566 assert!(limiter.try_acquire("temp"));
567 }
568 }
569
570 #[test]
571 fn sliding_window_tenant_is_supported() {
572 let limiter = TenantRateLimiter::new();
573 limiter.set_tenant_config("sw", RateLimitConfig::new(3.0).with_sliding_window(1));
575 assert!(limiter.try_acquire("sw"));
576 assert!(limiter.try_acquire("sw"));
577 assert!(limiter.try_acquire("sw"));
578 assert!(!limiter.try_acquire("sw"));
579 }
580
581 #[test]
582 fn usage_absent_for_unmaterialized_tenant() {
583 let limiter = TenantRateLimiter::with_default(RateLimitConfig::new(10.0));
584 assert!(limiter.usage("never_seen").is_none());
585 assert_eq!(limiter.tracked_count(), 0);
586 let _ = limiter.try_acquire("now_seen");
587 assert_eq!(limiter.tracked_count(), 1);
588 assert!(limiter.usage("now_seen").is_some());
589 }
590
591 #[test]
592 fn time_until_available_for_exhausted_quota_is_max() {
593 let limiter = TenantRateLimiter::new();
594 limiter.set_tenant_policy(
595 "q",
596 TenantRateLimit::new(RateLimitConfig::new(1_000_000.0).with_burst(100)).with_quota(1),
597 );
598 assert!(limiter.try_acquire("q"));
599 assert!(!limiter.try_acquire("q"));
600 assert_eq!(limiter.time_until_available("q"), Duration::MAX);
601 }
602
603 #[test]
604 fn has_capacity_reflects_state() {
605 let limiter = TenantRateLimiter::new();
606 limiter.set_tenant_config("c", RateLimitConfig::new(0.0001).with_burst(1));
607 assert!(limiter.has_capacity("c"));
609 assert!(limiter.try_acquire("c"));
610 assert!(!limiter.has_capacity("c"));
612 }
613
614 #[test]
615 fn policy_from_config_conversion() {
616 let policy: TenantRateLimit = RateLimitConfig::new(50.0).into();
617 assert!(policy.quota.is_none());
618 assert!((policy.config.rate - 50.0).abs() < f64::EPSILON);
619 }
620
621 #[test]
622 fn tenant_rate_limit_serde_round_trip() {
623 let policy =
624 TenantRateLimit::new(RateLimitConfig::new(25.0).with_burst(50)).with_quota(500);
625 let json = serde_json::to_string(&policy).expect("serialize");
626 let parsed: TenantRateLimit = serde_json::from_str(&json).expect("deserialize");
627 assert_eq!(parsed.quota, Some(500));
628 assert_eq!(parsed.config.burst, Some(50));
629 }
630
631 #[test]
632 fn concurrent_tenants_respect_caps() {
633 use std::thread;
634
635 let limiter = TenantRateLimiter::new();
636 limiter.set_tenant_config("shared", RateLimitConfig::new(0.001).with_burst(10));
638
639 let handles: Vec<_> = (0..4)
640 .map(|_| {
641 let l = limiter.clone();
642 thread::spawn(move || {
643 let mut count = 0;
644 for _ in 0..10 {
645 if l.try_acquire("shared") {
646 count += 1;
647 }
648 }
649 count
650 })
651 })
652 .collect();
653
654 let total: usize = handles
655 .into_iter()
656 .map(|h| h.join().expect("thread joins"))
657 .sum();
658 assert!(total <= 10, "granted {total} permits, expected <= 10");
660 }
661}