#![deny(
clippy::all,
clippy::missing_const_for_fn,
clippy::missing_docs_in_private_items,
clippy::pedantic,
future_incompatible,
missing_docs,
nonstandard_style,
rust_2018_idioms,
rustdoc::broken_intra_doc_links,
unsafe_code,
unused
)]
#![allow(
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::unnecessary_wraps,
clippy::used_underscore_binding
)]
#![doc = include_str!("../README.md")]
pub mod headers;
pub mod in_memory;
pub mod request;
pub mod ticket;
pub use self::{
headers::RatelimitHeaders,
in_memory::InMemoryRatelimiter,
request::{Method, Path},
};
use self::ticket::{TicketReceiver, TicketSender};
use futures_util::FutureExt;
use std::{
error::Error,
fmt::Debug,
future::Future,
pin::Pin,
time::{Duration, Instant},
};
pub struct Bucket {
limit: u64,
remaining: u64,
reset_after: Duration,
started_at: Option<Instant>,
}
impl Bucket {
#[must_use]
pub const fn new(
limit: u64,
remaining: u64,
reset_after: Duration,
started_at: Option<Instant>,
) -> Self {
Self {
limit,
remaining,
reset_after,
started_at,
}
}
#[must_use]
pub const fn limit(&self) -> u64 {
self.limit
}
#[must_use]
pub const fn remaining(&self) -> u64 {
self.remaining
}
#[must_use]
pub const fn reset_after(&self) -> Duration {
self.reset_after
}
#[must_use]
pub const fn started_at(&self) -> Option<Instant> {
self.started_at
}
#[must_use]
pub fn time_remaining(&self) -> Option<Duration> {
let reset_at = self.started_at? + self.reset_after;
reset_at.checked_duration_since(Instant::now())
}
}
pub type GenericError = Box<dyn Error + Send + Sync>;
pub type GetBucketFuture =
Pin<Box<dyn Future<Output = Result<Option<Bucket>, GenericError>> + Send + 'static>>;
pub type IsGloballyLockedFuture =
Pin<Box<dyn Future<Output = Result<bool, GenericError>> + Send + 'static>>;
pub type HasBucketFuture =
Pin<Box<dyn Future<Output = Result<bool, GenericError>> + Send + 'static>>;
pub type GetTicketFuture =
Pin<Box<dyn Future<Output = Result<TicketReceiver, GenericError>> + Send + 'static>>;
pub type WaitForTicketFuture =
Pin<Box<dyn Future<Output = Result<TicketSender, GenericError>> + Send + 'static>>;
pub trait Ratelimiter: Debug + Send + Sync {
fn bucket(&self, path: &Path) -> GetBucketFuture;
fn is_globally_locked(&self) -> IsGloballyLockedFuture;
fn has(&self, path: &Path) -> HasBucketFuture;
fn ticket(&self, path: Path) -> GetTicketFuture;
fn wait_for_ticket(&self, path: Path) -> WaitForTicketFuture {
Box::pin(self.ticket(path).then(|maybe_rx| async move {
match maybe_rx {
Ok(rx) => rx.await.map_err(From::from),
Err(e) => Err(e),
}
}))
}
}