use std::time::Duration;
use http::Request;
use super::{error::RateLimitError, response::RateLimitFields, store::Usage};
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Policy {
pub name: String,
pub limit: u64,
pub window: Duration,
pub used: u64,
pub reset_after: Duration,
}
impl Policy {
pub(super) fn from_usage(name: String, window: Duration, limit: u64, usage: Usage) -> Result<Self, RateLimitError> {
if usage.used == 0 {
return Err(RateLimitError::Store(
String::from("invalid_usage"),
String::from("rate-limit store returned zero usage"),
));
}
Ok(Self {
name,
limit,
window,
used: usage.used,
reset_after: usage.reset_after,
})
}
pub fn remaining(&self) -> u64 {
self.limit.saturating_sub(self.used)
}
pub fn is_rate_limited(&self) -> bool {
self.used > self.limit
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RateLimitContext {
policies: Vec<Policy>,
}
impl RateLimitContext {
pub const fn new() -> Self {
Self { policies: Vec::new() }
}
pub fn policies(&self) -> &[Policy] {
&self.policies
}
}
#[derive(Clone, Debug)]
pub(super) struct ResponseMetadata {
pub(super) policy: Policy,
pub(super) fields: RateLimitFields,
}
impl ResponseMetadata {
pub(super) fn new(policy: Policy, fields: RateLimitFields) -> Self {
Self { policy, fields }
}
}
pub(super) fn make_key(policy_name: &str, client_key: &str) -> String {
format!("{}:{}", escape_key_part(policy_name), escape_key_part(client_key))
}
fn escape_key_part(value: &str) -> String {
value.replace('%', "%25").replace(':', "%3A")
}
pub(super) fn append_context<B>(request: &mut Request<B>, metadata: &ResponseMetadata) {
request
.extensions_mut()
.get_or_insert_default::<RateLimitContext>()
.policies
.push(metadata.policy.clone());
}