1#![allow(unused)]
2
3pub mod builder;
4
5use anyhow::{anyhow, Result};
6use redis::Client as RedisClient;
7use std::time::{Duration, SystemTime, UNIX_EPOCH};
8
9pub enum Limiter {
10 FixedWindow {
11 tokens: u64,
12 window: Duration,
13 },
14 SlidingLogs {
15 tokens: u64,
16 window: Duration,
17 },
18 SlidingWindow {
19 tokens: u64,
20 window: Duration,
21 },
22 TokenBucket {
23 refill_rate: u64,
24 interval: Duration,
25 max_tokens: u64,
26 },
27}
28
29pub enum Response {
30 Success {
31 limit: u64,
32 remaining: u64,
33 reset: Duration,
34 },
35 Failure {
36 limit: u64,
37 reset: Duration,
38 },
39}
40
41pub struct RateLimit {
42 redis: RedisClient,
43 limiter: Limiter,
44 prefix: String,
45 timeout: Option<Duration>,
46 analytics: bool,
47}
48
49impl RateLimit {
50 pub fn builder() -> builder::RateLimitBuilder {
52 builder::RateLimitBuilder::default()
53 }
54
55 pub fn limit<T>(&self, identifier: T) -> Result<Response>
59 where
60 T: Into<String> + std::fmt::Display,
61 {
62 return match self.limiter {
63 Limiter::FixedWindow { tokens, window } => {
64 let script = redis::Script::new(
65 r#"
66 local key = KEYS[1]
67 local window = ARGV[1]
68
69 local r = redis.call("INCR", key)
70 if r == 1 then
71 -- The first time this key is set, the value will be 1.
72 -- So we only need the expire command once
73 redis.call("PEXPIRE", key, window)
74 end
75
76 return r
77 "#,
78 );
79
80 let window_duration = window.as_millis();
81
82 let since_epoch = SystemTime::now()
83 .duration_since(UNIX_EPOCH)
84 .expect("Time went backwards")
85 .as_millis();
86 let bucket = since_epoch / window_duration;
87 let key = format!("{}:{}", identifier, bucket);
88
89 let mut redis = self.redis.get_connection()?;
92 let tokens_used: u64 = script
93 .key(key)
94 .arg(format!("{}", window_duration))
95 .invoke(&mut redis)?;
96
97 let reset = Duration::from_millis(((bucket + 1) * window_duration) as u64);
98
99 if tokens_used <= tokens {
100 Ok(Response::Success {
101 limit: tokens,
102 remaining: tokens - tokens_used,
103 reset,
104 })
105 } else {
106 Ok(Response::Failure {
107 limit: tokens,
108 reset,
109 })
110 }
111 }
112 _ => unimplemented!("Limiter method not implemented."),
113 };
114 }
115}