use std::{
sync::Arc,
time::{Duration, Instant},
};
use async_trait::async_trait;
use moka::{sync::Cache, Expiry};
use super::CacheDriver;
use crate::cache::CacheResult;
use crate::config::InMemCacheConfig;
#[must_use]
pub fn new(config: &InMemCacheConfig) -> crate::cache::Cache {
let cache: Cache<String, (Expiration, String)> = Cache::builder()
.max_capacity(config.max_capacity)
.expire_after(InMemExpiry)
.build();
crate::cache::Cache::new(Inmem::from(cache))
}
#[derive(Debug)]
pub struct Inmem {
cache: Cache<String, (Expiration, String)>,
}
impl Inmem {
#[must_use]
pub fn from(cache: Cache<String, (Expiration, String)>) -> Box<dyn CacheDriver> {
Box::new(Self { cache })
}
}
#[async_trait]
impl CacheDriver for Inmem {
async fn contains_key(&self, key: &str) -> CacheResult<bool> {
Ok(self.cache.contains_key(key))
}
async fn get(&self, key: &str) -> CacheResult<Option<String>> {
let result = self.cache.get(key);
match result {
None => Ok(None),
Some(v) => Ok(Some(v.1)),
}
}
async fn insert(&self, key: &str, value: &str) -> CacheResult<()> {
self.cache.insert(
key.to_string(),
(Expiration::Never, Arc::new(value).to_string()),
);
Ok(())
}
async fn insert_with_expiry(
&self,
key: &str,
value: &str,
duration: Duration,
) -> CacheResult<()> {
self.cache.insert(
key.to_string(),
(
Expiration::AfterDuration(duration),
Arc::new(value).to_string(),
),
);
Ok(())
}
async fn remove(&self, key: &str) -> CacheResult<()> {
self.cache.remove(key);
Ok(())
}
async fn clear(&self) -> CacheResult<()> {
self.cache.invalidate_all();
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Expiration {
Never,
AfterDuration(Duration),
}
impl Expiration {
#[must_use]
pub fn as_duration(&self) -> Option<Duration> {
match self {
Self::Never => None,
Self::AfterDuration(d) => Some(*d),
}
}
}
pub struct InMemExpiry;
impl Expiry<String, (Expiration, String)> for InMemExpiry {
fn expire_after_create(
&self,
_key: &String,
value: &(Expiration, String),
_current_time: Instant,
) -> Option<Duration> {
value.0.as_duration()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::InMemCacheConfig;
fn create_test_config() -> InMemCacheConfig {
InMemCacheConfig { max_capacity: 100 }
}
#[tokio::test]
async fn is_contains_key() {
let config = create_test_config();
let mem = new(&config);
assert!(!mem.contains_key("key").await.unwrap());
assert!(mem.insert("key", "loco").await.is_ok());
assert!(mem.contains_key("key").await.unwrap());
}
#[tokio::test]
async fn can_get_key_value() {
let config = create_test_config();
let mem = new(&config);
assert!(mem.insert("key", "loco").await.is_ok());
assert_eq!(
mem.get::<String>("key").await.unwrap(),
Some("loco".to_string())
);
assert_eq!(mem.get::<String>("not-found").await.unwrap(), None);
}
#[tokio::test]
async fn can_remove_key() {
let config = create_test_config();
let mem = new(&config);
assert!(mem.insert("key", "loco").await.is_ok());
assert!(mem.contains_key("key").await.unwrap());
mem.remove("key").await.unwrap();
assert!(!mem.contains_key("key").await.unwrap());
}
#[tokio::test]
async fn can_clear() {
let config = create_test_config();
let mem = new(&config);
let keys = vec!["key", "key2", "key3"];
for key in &keys {
assert!(mem.insert(key, "loco").await.is_ok());
}
for key in &keys {
assert!(mem.contains_key(key).await.is_ok());
}
assert!(mem.clear().await.is_ok());
for key in &keys {
assert!(!mem.contains_key(key).await.unwrap());
}
}
}