use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use http::{HeaderValue, Request, Response, StatusCode, header};
use pin_project_lite::pin_project;
use tower_layer::Layer;
use tower_service::Service;
use crate::{Decision, Guard};
pub trait Key {
fn check(&self, guard: &Guard) -> Decision;
}
macro_rules! key_in_the_default_class {
($($t:ty),*) => {$(
impl Key for $t {
fn check(&self, guard: &Guard) -> Decision {
guard.check(self)
}
}
)*};
}
key_in_the_default_class!(
String,
&str,
Box<str>,
std::sync::Arc<str>,
std::borrow::Cow<'_, str>
);
impl<C: AsRef<str>, K: AsRef<str>> Key for (C, K) {
fn check(&self, guard: &Guard) -> Decision {
guard.check_in(self.0.as_ref(), self.1.as_ref())
}
}
#[derive(Debug, Clone)]
pub struct RateLimitLayer<F> {
guard: Guard,
key: F,
}
impl<F> RateLimitLayer<F> {
pub fn new(guard: Guard, key: F) -> Self {
Self { guard, key }
}
}
impl<S, F: Clone> Layer<S> for RateLimitLayer<F> {
type Service = RateLimit<S, F>;
fn layer(&self, inner: S) -> Self::Service {
RateLimit {
inner,
guard: self.guard.clone(),
key: self.key.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct RateLimit<S, F> {
inner: S,
guard: Guard,
key: F,
}
impl<S, F, K, ReqBody, ResBody> Service<Request<ReqBody>> for RateLimit<S, F>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>>,
F: Fn(&Request<ReqBody>) -> Option<K>,
K: Key,
ResBody: Default,
{
type Response = Response<ResBody>;
type Error = S::Error;
type Future = ResponseFuture<S::Future, ResBody>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
let denied = (self.key)(&req).and_then(|key| match key.check(&self.guard) {
Decision::Allow => None,
Decision::Deny { retry_after } => Some(retry_after),
});
let kind = match denied {
None => Kind::Served {
future: self.inner.call(req),
},
Some(retry_after) => Kind::Denied {
response: Some(too_many_requests(retry_after)),
},
};
ResponseFuture { kind }
}
}
fn too_many_requests<B: Default>(retry_after: Duration) -> Response<B> {
let seconds = retry_after.as_millis().div_ceil(1000).max(1);
let mut response = Response::new(B::default());
*response.status_mut() = StatusCode::TOO_MANY_REQUESTS;
response
.headers_mut()
.insert(header::RETRY_AFTER, HeaderValue::from(seconds as u64));
response
}
pin_project! {
pub struct ResponseFuture<F, B> {
#[pin]
kind: Kind<F, B>,
}
}
pin_project! {
#[project = KindProj]
enum Kind<F, B> {
Served { #[pin] future: F },
Denied { response: Option<Response<B>> },
}
}
impl<F, B, E> Future for ResponseFuture<F, B>
where
F: Future<Output = Result<Response<B>, E>>,
{
type Output = Result<Response<B>, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project().kind.project() {
KindProj::Served { future } => future.poll(cx),
KindProj::Denied { response } => {
Poll::Ready(Ok(response.take().expect("polled after it completed")))
}
}
}
}