use std::fmt;
use dashmap::mapref::entry::Entry;
use dashmap::DashMap;
use crate::gcra::check_gcra;
use crate::quota::{Nanos, Quota};
use crate::storage::{Storage, StorageFuture, StorageKey};
pub struct MemoryStorage {
state: DashMap<(String, String), Nanos>,
}
impl MemoryStorage {
pub fn new() -> Self {
Self {
state: DashMap::new(),
}
}
pub fn len(&self) -> usize {
self.state.len()
}
pub fn is_empty(&self) -> bool {
self.state.is_empty()
}
pub fn retain_active(&self, now: Nanos) {
self.state.retain(|_, tat| *tat >= now);
}
}
impl Default for MemoryStorage {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for MemoryStorage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MemoryStorage")
.field("entries", &self.state.len())
.finish()
}
}
impl Storage for MemoryStorage {
fn check_and_update<'a>(
&'a self,
key: StorageKey<'a>,
quota: &'a Quota,
cost: u32,
now: Nanos,
) -> StorageFuture<'a> {
let ei = quota.emission_interval_nanos();
let bo = quota.burst_offset_nanos();
let entry = (key.user_id.to_owned(), key.tier.to_owned());
let result = match self.state.entry(entry) {
Entry::Occupied(mut slot) => {
check_gcra(Some(*slot.get()), now, ei, bo, cost).map(|(new_tat, info)| {
slot.insert(new_tat);
info
})
}
Entry::Vacant(slot) => check_gcra(None, now, ei, bo, cost).map(|(new_tat, info)| {
slot.insert(new_tat);
info
}),
};
Box::pin(std::future::ready(Ok(result)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_shows_entry_count_but_not_keys() {
let storage = MemoryStorage::new();
storage
.state
.insert(("secret-api-key".to_owned(), "free".to_owned()), 1);
let out = format!("{:?}", storage);
assert!(out.contains("entries: 1"), "{out}");
assert!(!out.contains("secret-api-key"), "{out}");
}
}