Skip to main content

tower_rate_limiter/limiter/
policy.rs

1//! Policy state and response metadata behind [`super::future::ResponseFuture`].
2//!
3//! Owns scoped Key construction, Policy construction, response metadata, and
4//! request context annotation.
5//!
6//! [`Policy`] is the source of truth for one charged request.
7//! [`ResponseMetadata`] adds the private response-field configuration.
8
9use std::time::Duration;
10
11use http::Request;
12
13use super::{error::RateLimitError, response::RateLimitFields, store::Usage};
14
15/// A policy's rate-limit state exposed to a downstream handler.
16#[non_exhaustive]
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct Policy {
19    /// The policy identifier.
20    pub name: String,
21    /// The resolved quota limit.
22    pub limit: u64,
23    /// The policy's window.
24    pub window: Duration,
25    /// Used requests in the current window.
26    pub used: u64,
27    /// Time remaining until the window resets.
28    pub reset_after: Duration,
29}
30
31impl Policy {
32    /// Build one policy state from a successful Store result.
33    pub(super) fn from_usage(name: String, window: Duration, limit: u64, usage: Usage) -> Result<Self, RateLimitError> {
34        if usage.used == 0 {
35            return Err(RateLimitError::Store(
36                String::from("invalid_usage"),
37                String::from("rate-limit store returned zero usage"),
38            ));
39        }
40
41        Ok(Self {
42            name,
43            limit,
44            window,
45            used: usage.used,
46            reset_after: usage.reset_after,
47        })
48    }
49
50    /// Return the remaining quota after the current request.
51    pub fn remaining(&self) -> u64 {
52        self.limit.saturating_sub(self.used)
53    }
54
55    /// Return true if the policy is rate limited.
56    pub fn is_rate_limited(&self) -> bool {
57        self.used > self.limit
58    }
59}
60
61/// Read-only rate-limit state carried in request extensions for allowed requests.
62#[derive(Clone, Debug, Default, PartialEq, Eq)]
63pub struct RateLimitContext {
64    /// The policies in the context.
65    policies: Vec<Policy>,
66}
67
68impl RateLimitContext {
69    /// Construct an empty context.
70    pub const fn new() -> Self {
71        Self { policies: Vec::new() }
72    }
73
74    /// Borrow all policy entries in composition order.
75    pub fn policies(&self) -> &[Policy] {
76        &self.policies
77    }
78}
79
80/// Internal response-field configuration for one charged policy.
81#[derive(Clone, Debug)]
82pub(super) struct ResponseMetadata {
83    pub(super) policy: Policy,
84    pub(super) fields: RateLimitFields,
85}
86
87impl ResponseMetadata {
88    pub(super) fn new(policy: Policy, fields: RateLimitFields) -> Self {
89        Self { policy, fields }
90    }
91}
92
93/// Build the scoped Key passed toward a [`super::Store`].
94pub(super) fn make_key(policy_name: &str, client_key: &str) -> String {
95    format!("{}:{}", escape_key_part(policy_name), escape_key_part(client_key))
96}
97
98fn escape_key_part(value: &str) -> String {
99    value.replace('%', "%25").replace(':', "%3A")
100}
101
102/// Append the public policy projection to the request extensions.
103pub(super) fn append_context<B>(request: &mut Request<B>, metadata: &ResponseMetadata) {
104    request
105        .extensions_mut()
106        .get_or_insert_default::<RateLimitContext>()
107        .policies
108        .push(metadata.policy.clone());
109}