sz_orm_core/
rate_limiter.rs1use std::fmt;
10
11#[derive(Debug, Clone)]
13pub struct RateLimitResult {
14 pub allowed: bool,
16 pub remaining: u64,
18 pub reset_at: i64,
20}
21
22impl RateLimitResult {
23 pub fn allowed(remaining: u64, reset_at: i64) -> Self {
25 Self {
26 allowed: true,
27 remaining,
28 reset_at,
29 }
30 }
31
32 pub fn rejected(remaining: u64, reset_at: i64) -> Self {
34 Self {
35 allowed: false,
36 remaining,
37 reset_at,
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct RateLimitError(pub String);
47
48impl fmt::Display for RateLimitError {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(f, "rate limiter error: {}", self.0)
51 }
52}
53
54impl std::error::Error for RateLimitError {}
55
56pub trait RateLimiter: Send + Sync {
58 fn try_acquire(&self, key: &str) -> Result<RateLimitResult, RateLimitError>;
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn test_rate_limit_result_allowed() {
68 let r = RateLimitResult::allowed(9, 1_700_000_000_000);
69 assert!(r.allowed);
70 assert_eq!(r.remaining, 9);
71 }
72
73 #[test]
74 fn test_rate_limit_result_rejected() {
75 let r = RateLimitResult::rejected(0, 1_700_000_000_000);
76 assert!(!r.allowed);
77 assert_eq!(r.remaining, 0);
78 }
79
80 #[test]
81 fn test_rate_limit_error_display() {
82 let e = RateLimitError("backend down".to_string());
83 assert!(e.to_string().contains("backend down"));
84 }
85
86 struct AlwaysAllow;
88
89 impl RateLimiter for AlwaysAllow {
90 fn try_acquire(&self, _key: &str) -> Result<RateLimitResult, RateLimitError> {
91 Ok(RateLimitResult::allowed(1, 0))
92 }
93 }
94
95 #[test]
96 fn test_trait_dispatch() {
97 let limiter: &dyn RateLimiter = &AlwaysAllow;
98 let r = limiter.try_acquire("test-key").unwrap();
99 assert!(r.allowed);
100 }
101}