use std::{fmt, sync::Arc, time::Duration};
use http::Request;
use super::{
error::ConfigError,
layer::RateLimitLayer,
limit::LimitProvider,
response::{DefaultResponseFactory, RateLimitFields},
store::{Store, StoreFailureMode},
};
const MINIMUM_WINDOW: Duration = Duration::from_millis(1);
pub(crate) type KeyEncoder = Box<dyn Fn(&str) -> String + Send + Sync>;
pub(crate) type SkipPredicate = Box<dyn Fn(&Request<()>) -> bool + Send + Sync>;
pub(crate) fn check_skip_predicate<B>(predicate: Option<&SkipPredicate>, request: Request<B>) -> (bool, Request<B>) {
let Some(predicate) = predicate else {
return (false, request);
};
let (parts, body) = request.into_parts();
let request_head = Request::from_parts(parts, ());
let should_skip = predicate(&request_head);
let (parts, ()) = request_head.into_parts();
(should_skip, Request::from_parts(parts, body))
}
pub struct RateLimitBuilder<K, S = (), P = u64, F = DefaultResponseFactory> {
key_extractor: K,
store: S,
limit_provider: P,
response_factory: F,
config: RateLimitConfig,
}
impl<K> RateLimitBuilder<K> {
pub(crate) fn new(key_extractor: K) -> Self {
Self {
key_extractor,
store: (),
limit_provider: 1,
response_factory: DefaultResponseFactory,
config: RateLimitConfig {
policy_name: String::from("default-policy"),
window: Duration::from_secs(60),
key_encoder: None,
skip_predicate: None,
store_failure_mode: StoreFailureMode::default(),
#[cfg(feature = "tracing")]
store_failure_tracing_level: tracing::Level::WARN,
rate_limit_fields: RateLimitFields::default(),
},
}
}
}
impl<K, S, P, F> RateLimitBuilder<K, S, P, F> {
pub fn with_store<S2>(self, store: S2) -> RateLimitBuilder<K, S2, P, F> {
let Self {
key_extractor,
limit_provider,
response_factory,
config,
..
} = self;
RateLimitBuilder {
key_extractor,
store,
limit_provider,
response_factory,
config,
}
}
pub fn limit(self, limit: u64) -> RateLimitBuilder<K, S, u64, F> {
let Self {
key_extractor,
store,
response_factory,
config,
..
} = self;
RateLimitBuilder {
key_extractor,
store,
limit_provider: limit,
response_factory,
config,
}
}
pub fn limit_provider<P2>(self, limit_provider: P2) -> RateLimitBuilder<K, S, P2, F> {
let Self {
key_extractor,
store,
response_factory,
config,
..
} = self;
RateLimitBuilder {
key_extractor,
store,
limit_provider,
response_factory,
config,
}
}
pub fn response_factory<F2>(self, response_factory: F2) -> RateLimitBuilder<K, S, P, F2> {
let Self {
key_extractor,
store,
limit_provider,
config,
..
} = self;
RateLimitBuilder {
key_extractor,
store,
limit_provider,
response_factory,
config,
}
}
pub fn window(mut self, window: Duration) -> Self {
self.config.window = window;
self
}
pub fn policy_name(mut self, policy_name: impl Into<String>) -> Self {
self.config.policy_name = policy_name.into();
self
}
pub fn with_key_encoder<E>(mut self, encoder: E) -> Self
where
E: Fn(&str) -> String + Send + Sync + 'static,
{
self.config.key_encoder = Some(Box::new(encoder));
self
}
pub fn skip<Predicate>(mut self, predicate: Predicate) -> Self
where
Predicate: Fn(&Request<()>) -> bool + Send + Sync + 'static,
{
self.config.skip_predicate = Some(Box::new(predicate));
self
}
pub fn store_failure_mode(mut self, mode: StoreFailureMode) -> Self {
self.config.store_failure_mode = mode;
self
}
#[cfg(feature = "tracing")]
pub fn store_failure_tracing_level(mut self, level: tracing::Level) -> Self {
self.config.store_failure_tracing_level = level;
self
}
pub fn rate_limit_fields(mut self, fields: RateLimitFields) -> Self {
self.config.rate_limit_fields = fields;
self
}
fn validate(&self) -> Result<(), ConfigError> {
if self.config.window < MINIMUM_WINDOW {
return Err(ConfigError::WindowTooShort(self.config.window, MINIMUM_WINDOW));
}
if self.config.policy_name.is_empty() {
return Err(ConfigError::EmptyPolicyName);
}
Ok(())
}
}
impl<K> RateLimitLayer<K, (), u64, DefaultResponseFactory> {
pub fn builder(key_extractor: K) -> RateLimitBuilder<K> {
RateLimitBuilder::new(key_extractor)
}
}
impl<K, S, P, F> RateLimitBuilder<K, S, P, F>
where
S: Store,
P: LimitProvider,
{
pub fn build(self) -> Result<RateLimitLayer<K, S, P, F>, ConfigError> {
self.validate()?;
Ok(RateLimitLayer {
key_extractor: self.key_extractor,
store: self.store,
limit_provider: self.limit_provider,
response_factory: self.response_factory,
config: Arc::new(self.config),
})
}
}
pub(crate) struct RateLimitConfig {
pub(crate) policy_name: String,
pub(crate) window: Duration,
pub(crate) key_encoder: Option<KeyEncoder>,
pub(crate) skip_predicate: Option<SkipPredicate>,
pub(crate) store_failure_mode: StoreFailureMode,
#[cfg(feature = "tracing")]
pub(crate) store_failure_tracing_level: tracing::Level,
pub(crate) rate_limit_fields: RateLimitFields,
}
impl fmt::Debug for RateLimitConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("RateLimitConfig");
debug
.field("policy_name", &self.policy_name)
.field("window", &self.window)
.field("has_key_encoder", &self.key_encoder.is_some())
.field("has_skip_predicate", &self.skip_predicate.is_some())
.field("store_failure_mode", &self.store_failure_mode);
#[cfg(feature = "tracing")]
debug.field("store_failure_tracing_level", &self.store_failure_tracing_level);
debug.field("rate_limit_fields", &self.rate_limit_fields).finish()
}
}