governor/
middleware.rs

1//! Additional, customizable behavior for rate limiters.
2//!
3//! Rate-limiting middleware follows the principle that basic
4//! rate-limiting should be very cheap, and unless users desire more
5//! behavior, they should not pay any extra price.
6//!
7//! However, if you do desire more information about what the
8//! rate-limiter does (or the ability to install hooks in the
9//! decision-making process), you can. The [`RateLimitingMiddleware`]
10//! trait in this module allows you to customize:
11//!
12//! * Any additional code that gets run when a rate-limiting decision is made.
13//! * What value is returned in the positive or negative case.
14//!
15//! Writing middleware does **not** let you override rate-limiting
16//! decisions: They remain either positive (returning `Ok`) or negative
17//! (returning `Err`). However, you can override the values returned
18//! inside the Result for either decision.
19//!
20//! This crate ships two middlewares (named after their behavior in the
21//! positive outcome):
22//!
23//! * The cheapest still-useful one, [`NoOpMiddleware`], named after its
24//!   behavior in the positive case. In the positive case it returns
25//!   `Ok(())`; in the negative case, `Err(`[`NotUntil`]`)`.
26//!
27//! * A more informative middleware, [`StateInformationMiddleware`], which
28//!   returns `Ok(`[`StateSnapshot`]`)`, or
29//!   `Err(`[`NotUntil`]`)`.
30//!
31//! ## Using a custom middleware
32//!
33//! Middlewares are attached to the
34//! [`RateLimiter`][crate::RateLimiter] at construction time using
35//! [`RateLimiter::with_middleware`][crate::RateLimiter::with_middleware]:
36//!
37//! ```rust
38//! # #[cfg(feature = "std")]
39//! # fn main () {
40//! # use nonzero_ext::nonzero;
41//! use governor::{RateLimiter, Quota, middleware::StateInformationMiddleware};
42//! let lim = RateLimiter::direct(Quota::per_hour(nonzero!(1_u32)))
43//!     .with_middleware::<StateInformationMiddleware>();
44//!
45//! // A positive outcome with additional information:
46//! assert!(
47//!     lim.check()
48//!         // Here we receive an Ok(StateSnapshot):
49//!         .map(|outcome| assert_eq!(outcome.remaining_burst_capacity(), 0))
50//!         .is_ok()
51//! );
52//!
53//! // The negative case:
54//! assert!(
55//!     lim.check()
56//!         // Here we receive Err(NotUntil):
57//!         .map_err(|outcome| assert_eq!(outcome.quota().burst_size().get(), 1))
58//!         .is_err()
59//! );
60//! # }
61//! # #[cfg(not(feature = "std"))]
62//! # fn main() {}
63//! ```
64//!
65//! You can define your own middleware by `impl`ing [`RateLimitingMiddleware`].
66use core::fmt;
67use core::{cmp, marker::PhantomData};
68
69use crate::{clock, nanos::Nanos, NotUntil, Quota};
70
71/// Information about the rate-limiting state used to reach a decision.
72#[derive(Clone, PartialEq, Eq, Debug)]
73pub struct StateSnapshot {
74    /// The "weight" of a single packet in units of time.
75    t: Nanos,
76
77    /// The "tolerance" of the bucket.
78    ///
79    /// The total "burst capacity" of the bucket is `t + tau`.
80    tau: Nanos,
81
82    /// The time at which the measurement was taken.
83    pub(crate) time_of_measurement: Nanos,
84
85    /// The next time a cell is expected to arrive
86    pub(crate) tat: Nanos,
87}
88
89impl StateSnapshot {
90    #[inline]
91    pub(crate) fn new(t: Nanos, tau: Nanos, time_of_measurement: Nanos, tat: Nanos) -> Self {
92        Self {
93            t,
94            tau,
95            time_of_measurement,
96            tat,
97        }
98    }
99
100    /// Returns the quota used to make the rate limiting decision.
101    pub fn quota(&self) -> Quota {
102        Quota::from_gcra_parameters(self.t, self.tau)
103    }
104
105    /// Returns the number of cells that can be let through in
106    /// addition to a (possible) positive outcome.
107    ///
108    /// If this state snapshot is based on a negative rate limiting
109    /// outcome, this method returns 0.
110    pub fn remaining_burst_capacity(&self) -> u32 {
111        let t0 = self.time_of_measurement;
112        (cmp::min(
113            (t0 + self.tau + self.t).saturating_sub(self.tat).as_u64(),
114            (self.tau + self.t).as_u64(),
115        ) / self.t.as_u64()) as u32
116    }
117}
118
119/// Defines the behavior and return values of rate limiting decisions.
120///
121/// While the rate limiter defines whether a decision is positive, the
122/// middleware defines what additional values (other than `Ok` or `Err`)
123/// are returned from the [`RateLimiter`][crate::RateLimiter]'s check methods.
124///
125/// The default middleware in this crate is [`NoOpMiddleware`] (which does
126/// nothing in the positive case and returns [`NotUntil`] in the
127/// negative) - so it does only the smallest amount of work it needs to do
128/// in order to be useful to users.
129///
130/// Other middleware gets to adjust these trade-offs: The pre-made
131/// [`StateInformationMiddleware`] returns quota and burst capacity
132/// information, while custom middleware could return a set of HTTP
133/// headers or increment counters per each rate limiter key's decision.
134///
135/// # Defining your own middleware
136///
137/// Here's an example of a rate limiting middleware that does no
138/// computations at all on positive and negative outcomes: All the
139/// information that a caller will receive is that a request should be
140/// allowed or disallowed. This can allow for faster negative outcome
141/// handling, and is useful if you don't need to tell users when they
142/// can try again (or anything at all about their rate limiting
143/// status).
144///
145/// ```rust
146/// # use std::num::NonZeroU32;
147/// # use nonzero_ext::*;
148/// use governor::{middleware::{RateLimitingMiddleware, StateSnapshot},
149///                Quota, RateLimiter, clock::Reference};
150/// # #[cfg(feature = "std")]
151/// # fn main () {
152/// #[derive(Debug)]
153/// struct NullMiddleware;
154///
155/// impl<P: Reference> RateLimitingMiddleware<P> for NullMiddleware {
156///     type PositiveOutcome = ();
157///     type NegativeOutcome = ();
158///
159///     fn allow<K>(_key: &K, _state: impl Into<StateSnapshot>) -> Self::PositiveOutcome {}
160///     fn disallow<K>(_: &K, _: impl Into<StateSnapshot>, _: P) -> Self::NegativeOutcome {}
161/// }
162///
163/// let lim = RateLimiter::direct(Quota::per_hour(nonzero!(1_u32)))
164///     .with_middleware::<NullMiddleware>();
165///
166/// assert_eq!(lim.check(), Ok(()));
167/// assert_eq!(lim.check(), Err(()));
168/// # }
169/// # #[cfg(not(feature = "std"))]
170/// # fn main() {}
171/// ```
172pub trait RateLimitingMiddleware<P: clock::Reference>: fmt::Debug {
173    /// The type that's returned by the rate limiter when a cell is allowed.
174    ///
175    /// By default, rate limiters return `Ok(())`, which does not give
176    /// much information. By using custom middleware, users can obtain
177    /// more information about the rate limiter state that was used to
178    /// come to a decision. That state can then be used to pass
179    /// information downstream about, e.g. how much burst capacity is
180    /// remaining.
181    type PositiveOutcome: Sized;
182
183    /// The type that's returned by the rate limiter when a cell is *not* allowed.
184    ///
185    /// By default, rate limiters return `Err(NotUntil)`, which
186    /// allows interrogating the minimum amount of time to wait until
187    /// a client can expect to have a cell allowed again.
188    type NegativeOutcome: Sized;
189
190    /// Called when a positive rate-limiting decision is made.
191    ///
192    /// This function is able to affect the return type of
193    /// [RateLimiter.check](../struct.RateLimiter.html#method.check)
194    /// (and others) in the Ok case: Whatever is returned here is the
195    /// value of the Ok result returned from the check functions.
196    ///
197    /// The function is passed a snapshot of the rate-limiting state
198    /// updated to *after* the decision was reached: E.g., if there
199    /// was one cell left in the burst capacity before the decision
200    /// was reached, the [`StateSnapshot::remaining_burst_capacity`]
201    /// method will return 0.
202    fn allow<K>(key: &K, state: impl Into<StateSnapshot>) -> Self::PositiveOutcome;
203
204    /// Called when a negative rate-limiting decision is made (the
205    /// "not allowed but OK" case).
206    ///
207    /// This method returns whatever value is returned inside the
208    /// `Err` variant a [`RateLimiter`][crate::RateLimiter]'s check
209    /// method returns.
210    fn disallow<K>(
211        key: &K,
212        limiter: impl Into<StateSnapshot>,
213        start_time: P,
214    ) -> Self::NegativeOutcome;
215}
216
217/// A middleware that does nothing and returns `()` in the positive outcome.
218pub struct NoOpMiddleware<P: clock::Reference = <clock::DefaultClock as clock::Clock>::Instant> {
219    phantom: PhantomData<P>,
220}
221
222impl<P: clock::Reference> core::fmt::Debug for NoOpMiddleware<P> {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        write!(f, "NoOpMiddleware")
225    }
226}
227
228impl<P: clock::Reference> RateLimitingMiddleware<P> for NoOpMiddleware<P> {
229    /// By default, rate limiters return nothing other than an
230    /// indicator that the element should be let through.
231    type PositiveOutcome = ();
232
233    type NegativeOutcome = NotUntil<P>;
234
235    #[inline]
236    /// Returns `()` and has no side-effects.
237    fn allow<K>(_key: &K, _state: impl Into<StateSnapshot>) -> Self::PositiveOutcome {}
238
239    #[inline]
240    /// Returns the error indicating what
241    fn disallow<K>(
242        _key: &K,
243        state: impl Into<StateSnapshot>,
244        start_time: P,
245    ) -> Self::NegativeOutcome {
246        NotUntil::new(state.into(), start_time)
247    }
248}
249
250/// Middleware that returns the state of the rate limiter if a
251/// positive decision is reached.
252#[derive(Debug)]
253pub struct StateInformationMiddleware;
254
255impl<P: clock::Reference> RateLimitingMiddleware<P> for StateInformationMiddleware {
256    /// The state snapshot returned from the limiter.
257    type PositiveOutcome = StateSnapshot;
258
259    type NegativeOutcome = NotUntil<P>;
260
261    fn allow<K>(_key: &K, state: impl Into<StateSnapshot>) -> Self::PositiveOutcome {
262        state.into()
263    }
264
265    fn disallow<K>(
266        _key: &K,
267        state: impl Into<StateSnapshot>,
268        start_time: P,
269    ) -> Self::NegativeOutcome {
270        NotUntil::new(state.into(), start_time)
271    }
272}
273
274#[cfg(all(feature = "std", test))]
275mod test {
276    use std::time::Duration;
277
278    use super::*;
279
280    #[test]
281    fn middleware_impl_derives() {
282        assert_eq!(
283            format!("{StateInformationMiddleware:?}"),
284            "StateInformationMiddleware"
285        );
286        assert_eq!(
287            format!(
288                "{:?}",
289                NoOpMiddleware {
290                    phantom: PhantomData::<Duration>,
291                }
292            ),
293            "NoOpMiddleware"
294        );
295    }
296}