tower_rate_tier/storage/mod.rs
1/// In-memory storage backend using `DashMap`.
2pub mod memory;
3#[cfg(feature = "redis")]
4/// Redis storage backend for several instances sharing one rate limit.
5///
6/// Requires the `redis` feature.
7pub mod redis;
8
9use std::fmt;
10use std::future::Future;
11use std::pin::Pin;
12
13use crate::gcra::{RateLimitInfo, RateLimited};
14use crate::quota::{Nanos, Quota};
15
16/// The future type returned by [`Storage::check_and_update`].
17pub type StorageFuture<'a> = Pin<
18 Box<dyn Future<Output = Result<Result<RateLimitInfo, RateLimited>, StorageError>> + Send + 'a>,
19>;
20
21/// Identifies one rate-limit bucket: a user within a tier.
22///
23/// The parts stay separate so each backend can choose an encoding in which
24/// distinct pairs stay distinct, even when a user id or tier name contains
25/// the backend's separator.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub struct StorageKey<'a> {
28 /// The user identifier returned by the identifier.
29 pub user_id: &'a str,
30 /// The tier whose quota applies to this bucket.
31 pub tier: &'a str,
32}
33
34impl<'a> StorageKey<'a> {
35 /// Creates the key for `user_id` within `tier`.
36 pub fn new(user_id: &'a str, tier: &'a str) -> Self {
37 Self { user_id, tier }
38 }
39}
40
41/// Error returned when the storage backend fails (e.g., Redis connection lost).
42#[derive(Debug)]
43pub struct StorageError(pub Box<dyn std::error::Error + Send + Sync>);
44
45impl fmt::Display for StorageError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 write!(f, "storage error: {}", self.0)
48 }
49}
50
51impl std::error::Error for StorageError {
52 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
53 Some(self.0.as_ref())
54 }
55}
56
57/// Trait for rate limit state persistence backends.
58///
59/// Implementations must atomically check the current state and update it.
60///
61/// Each distinct [`StorageKey`] must map to its own state. A backend that
62/// joins the parts into one string must make that encoding injective, for
63/// example by length-prefixing or escaping the parts: a plain
64/// `format!("{user}:{tier}")` makes user `a:b` in tier `c` share state with
65/// user `a` in tier `b:c`.
66///
67/// The outer `Result` represents storage-level errors (e.g., Redis down).
68/// The inner `Result` represents the GCRA decision (allowed vs rate limited).
69pub trait Storage: Send + Sync + 'static {
70 /// Check rate limit and update state atomically.
71 ///
72 /// `now` comes from the rate limiter's [`Clock`](crate::clock::Clock),
73 /// whose epoch is local to one process. A backend shared between
74 /// processes should use its own time source instead.
75 ///
76 /// - `Ok(Ok(info))` — request allowed
77 /// - `Ok(Err(limited))` — request rate limited
78 /// - `Err(StorageError)` — storage backend failure
79 fn check_and_update<'a>(
80 &'a self,
81 key: StorageKey<'a>,
82 quota: &'a Quota,
83 cost: u32,
84 now: Nanos,
85 ) -> StorageFuture<'a>;
86}