use core::hash::Hash;
use crate::{
gcra::NotUntil, nanos::Nanos, quota::Quota, snapshot::RateSnapshot, state::RateLimiter, state::StateStore, timer,
};
#[cfg(test)]
use core::num::NonZeroU32;
#[cfg(test)]
use crate::{error::InsufficientCapacity, timer::Reference};
pub(crate) trait KeyedStateStore<K: Hash>: StateStore<Key = K> {}
impl<T, K: Hash> KeyedStateStore<K> for T
where
T: StateStore<Key = K>,
K: Eq + Clone + Hash,
{
}
impl<K> RateLimiter<K, DefaultKeyedStateStore<K>, timer::DefaultTimer>
where
K: Clone + Hash + Eq,
{
#[cfg(test)]
pub(crate) fn keyed(quota: Quota) -> Self {
let state = DefaultKeyedStateStore::default();
let clock = timer::DefaultTimer;
RateLimiter::new(quota, state, &clock)
}
pub(crate) fn hashmap(quota: Quota) -> Self {
let state = HashMapStateStore::default();
let timer = timer::DefaultTimer;
RateLimiter::new(quota, state, &timer)
}
}
impl<K, S, C> RateLimiter<K, S, C>
where
S: KeyedStateStore<K>,
K: Hash,
C: timer::Timer,
{
pub fn check_key(&self, key: &K) -> Result<RateSnapshot, NotUntil<C::Instant>> {
self.gcra
.test_and_update::<K, C::Instant, S>(self.start, key, &self.state, self.clock.now())
}
#[cfg(test)]
pub(crate) fn check_key_n(
&self,
key: &K,
n: NonZeroU32,
) -> Result<Result<RateSnapshot, NotUntil<C::Instant>>, InsufficientCapacity> {
self.gcra
.test_n_all_and_update::<K, C::Instant, S>(self.start, key, n, &self.state, self.clock.now())
}
}
pub(crate) trait ShrinkableKeyedStateStore<K: Hash>: KeyedStateStore<K> {
fn retain_recent(&self, drop_below: Nanos);
fn shrink_to_fit(&self) {}
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
}
#[cfg(test)]
impl<K, S, C> RateLimiter<K, S, C>
where
S: ShrinkableKeyedStateStore<K>,
K: Hash,
C: timer::Timer,
{
pub(crate) fn retain_recent(&self) {
let now = self.clock.now();
let drop_below = now.duration_since(self.start);
self.state.retain_recent(drop_below);
}
pub(crate) fn shrink_to_fit(&self) {
self.state.shrink_to_fit();
}
pub(crate) fn len(&self) -> usize {
self.state.len()
}
pub(crate) fn is_empty(&self) -> bool {
self.state.is_empty()
}
}
mod hashmap;
pub(crate) use hashmap::HashMapStateStore;
pub(crate) type DefaultKeyedStateStore<K> = HashMapStateStore<K>;
#[cfg(test)]
mod test {
use core::{marker::PhantomData, num::NonZeroU32};
use crate::timer::FakeRelativeClock;
use super::*;
#[test]
fn default_nonshrinkable_state_store_coverage() {
#[derive(Default)]
struct NaiveKeyedStateStore<K>(PhantomData<K>);
impl<K: Hash + Eq + Clone> StateStore for NaiveKeyedStateStore<K> {
type Key = K;
fn measure_and_replace<T, F, E>(&self, _key: &Self::Key, f: F) -> Result<T, E>
where
F: Fn(Option<Nanos>) -> Result<(T, Nanos), E>,
{
f(None).map(|(res, _)| res)
}
}
impl<K: Hash + Eq + Clone> ShrinkableKeyedStateStore<K> for NaiveKeyedStateStore<K> {
fn retain_recent(&self, _drop_below: Nanos) {
}
fn len(&self) -> usize {
0
}
fn is_empty(&self) -> bool {
true
}
}
let lim: RateLimiter<u32, NaiveKeyedStateStore<u32>, FakeRelativeClock> = RateLimiter::new(
Quota::per_second(NonZeroU32::new(1).unwrap()),
NaiveKeyedStateStore::default(),
&FakeRelativeClock::default(),
);
assert!(lim.check_key(&1u32).is_ok());
assert!(lim.is_empty());
assert_eq!(lim.len(), 0);
lim.retain_recent();
lim.shrink_to_fit();
}
}