actix_extensible_rate_limit/backend/mod.rs
1mod input_builder;
2
3#[cfg(feature = "dashmap")]
4#[cfg_attr(docsrs, doc(cfg(feature = "dashmap")))]
5pub mod memory;
6
7#[cfg(feature = "redis")]
8#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
9pub mod redis;
10
11pub use input_builder::{SimpleInputFunctionBuilder, SimpleInputFuture};
12use std::future::Future;
13
14use crate::HeaderCompatibleOutput;
15use actix_web::rt::time::Instant;
16use std::time::Duration;
17
18#[derive(Copy, Clone, Debug, Eq, PartialEq)]
19pub enum Decision {
20 Allowed,
21 Denied,
22}
23
24impl Decision {
25 pub fn from_allowed(allowed: bool) -> Self {
26 if allowed {
27 Self::Allowed
28 } else {
29 Self::Denied
30 }
31 }
32
33 pub fn is_allowed(self) -> bool {
34 matches!(self, Self::Allowed)
35 }
36
37 pub fn is_denied(self) -> bool {
38 matches!(self, Self::Denied)
39 }
40}
41
42/// Describes an implementation of a rate limiting store and algorithm.
43///
44/// A Backend is required to implement [Clone], usually this means wrapping your data store within
45/// an [Arc](std::sync::Arc), although many connection pools already do so internally; there is no
46/// need to wrap it twice.
47pub trait Backend<I: 'static = SimpleInput>: Clone {
48 type Output;
49 type RollbackToken;
50 type Error;
51
52 /// Process an incoming request.
53 ///
54 /// The input could include such things as a rate limit key, and the rate limit policy to be
55 /// applied.
56 ///
57 /// Returns a boolean of whether to allow or deny the request, arbitrary output that can be used
58 /// to transform the allowed and denied responses, and a token to allow the rate limit counter
59 /// to be rolled back in certain conditions.
60 fn request(
61 &self,
62 input: I,
63 ) -> impl Future<Output = Result<(Decision, Self::Output, Self::RollbackToken), Self::Error>>;
64
65 /// Under certain conditions we may not want to rollback the request operation.
66 ///
67 /// E.g. We may want to exclude 5xx errors from counting against a user's rate limit,
68 /// we can only exclude them after having already allowed the request through the rate limiter
69 /// in the first place, so we must therefore deduct from the rate limit counter afterwards.
70 ///
71 /// Note that if this function fails there is not much the [RateLimiter](crate::RateLimiter)
72 /// can do about it, given that the request has already been allowed.
73 ///
74 /// # Arguments
75 ///
76 /// * `token`: The token returned from the initial call to [Backend::request()].
77 fn rollback(&self, token: Self::RollbackToken)
78 -> impl Future<Output = Result<(), Self::Error>>;
79}
80
81/// A default [Backend] Input structure.
82///
83/// This may not be suitable for all use-cases.
84#[derive(Debug, Clone)]
85pub struct SimpleInput {
86 /// The rate limiting interval.
87 pub interval: Duration,
88 /// The total requests to be allowed within the interval.
89 pub max_requests: u64,
90 /// The rate limit key to be used for this request.
91 pub key: String,
92}
93
94/// A default [Backend::Output] structure.
95///
96/// This may not be suitable for all use-cases.
97#[derive(Debug, Clone)]
98pub struct SimpleOutput {
99 /// Total number of requests that are permitted within the rate limit interval.
100 pub limit: u64,
101 /// Number of requests that will be permitted until the limit resets.
102 pub remaining: u64,
103 /// Time at which the rate limit resets.
104 pub reset: Instant,
105}
106
107/// Additional functions for a [Backend] that uses [SimpleInput] and [SimpleOutput].
108pub trait SimpleBackend: Backend<SimpleInput, Output = SimpleOutput> {
109 /// Removes the bucket for a given rate limit key.
110 ///
111 /// Intended to be used to reset a key before changing the interval.
112 fn remove_key(&self, key: &str) -> impl Future<Output = Result<(), Self::Error>>;
113}
114
115impl HeaderCompatibleOutput for SimpleOutput {
116 fn limit(&self) -> u64 {
117 self.limit
118 }
119
120 fn remaining(&self) -> u64 {
121 self.remaining
122 }
123
124 /// Seconds until the rate limit resets (rounded upwards, so that it is guaranteed to be reset
125 /// after waiting for the duration).
126 fn seconds_until_reset(&self) -> u64 {
127 let millis = self
128 .reset
129 .saturating_duration_since(Instant::now())
130 .as_millis() as f64;
131 (millis / 1000f64).ceil() as u64
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[actix_web::test]
140 async fn test_seconds_until_reset() {
141 tokio::time::pause();
142 let output = SimpleOutput {
143 limit: 0,
144 remaining: 0,
145 reset: Instant::now() + Duration::from_secs(60),
146 };
147 tokio::time::advance(Duration::from_secs_f64(29.9)).await;
148 // Verify rounded upwards from 30.1
149 assert_eq!(output.seconds_until_reset(), 31);
150 }
151}