Skip to main content

tower_rate_limiter/limiter/
store.rs

1//! The core Store seam and returned usage.
2
3use std::{future::Future, time::Duration};
4
5use super::RateLimitError;
6
7/// The operating mode to use when the rate-limit store fails.
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
9pub enum StoreFailureMode {
10    /// Build an error response without calling the inner service.
11    #[default]
12    Reject,
13    /// Call the inner service without rate-limit metadata.
14    Allow,
15}
16
17/// The usage and reset duration returned by a store.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct Usage {
20    /// Total charged requests in the current window, including this increment.
21    pub used: u64,
22    /// Time remaining until the current window resets.
23    pub reset_after: Duration,
24}
25
26/// An asynchronous fixed-window rate-limit store.
27///
28/// Implementations receive a complete, policy-scoped key and must treat it as opaque. For each
29/// key, [`Store::increment`] must atomically create or increment one fixed window, start expiry on
30/// the first increment, and leave that expiry unchanged on later increments. The returned
31/// [`Usage`] includes the current increment, so `used` is at least one and `reset_after` is the
32/// remaining duration of the active window.
33///
34/// Clones of one Store value must observe the same counter state. Independently constructed Store
35/// values may use separate state. Backend failures are represented as [`RateLimitError::Store`].
36///
37/// This trait intentionally requires [`Clone`] because [`super::RateLimitLayer`] clones its Store
38/// into each produced Service and [`super::RateLimit::call`](tower_service::Service::call) clones it
39/// into each request's [`super::ResponseFuture`]. It does not require `Send`, `Sync`, or `'static`,
40/// and its Future has no unconditional `Send + 'static` bound. Frameworks should add those
41/// concurrency bounds at the integration point. For example, a generic Store passed to Axum's
42/// `Router::layer` normally needs `S: Store + Send + Sync + 'static` and
43/// `S::Future: Send + 'static` because Axum requires the resulting Layer, Service, and response
44/// Future to satisfy those bounds.
45pub trait Store: Clone {
46    /// The concrete future returned by [`Store::increment`].
47    type Future: Future<Output = Result<Usage, RateLimitError>>;
48
49    /// Atomically increment `key` for the fixed `window`.
50    fn increment(&self, key: &str, window: Duration) -> Self::Future;
51}