use crate::backend::interface::{BackendKind, CacheConnector, CacheReader, CacheWriter};
use crate::backend::score::{BackendScore, Scores};
use crate::error::Result;
use crate::impl_backend_builder;
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
#[derive(Clone)]
pub struct MokaMemoryBackend {
cache: Arc<moka::future::Cache<String, Vec<u8>>>,
capacity: u64,
}
impl_backend_builder!(MokaMemoryBackend, MokaMemoryBackendBuilder);
impl MokaMemoryBackend {
pub fn capacity(&self) -> u64 {
self.capacity
}
pub fn entry_count(&self) -> u64 {
self.cache.entry_count()
}
}
impl Default for MokaMemoryBackend {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for MokaMemoryBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MokaMemoryBackend")
.field("capacity", &self.capacity)
.field("entry_count", &self.cache.entry_count())
.finish()
}
}
#[async_trait]
impl CacheReader for MokaMemoryBackend {
async fn get(&self, key: &str) -> Result<Option<Vec<u8>>> {
Ok(self.cache.get(key).await)
}
async fn exists(&self, key: &str) -> Result<bool> {
Ok(self.cache.contains_key(key))
}
async fn ttl(&self, _key: &str) -> Result<Option<Duration>> {
Ok(None)
}
async fn len(&self) -> Result<u64> {
Ok(self.cache.entry_count())
}
async fn is_empty(&self) -> Result<bool> {
Ok(self.cache.entry_count() == 0)
}
async fn capacity(&self) -> Result<u64> {
Ok(self.capacity)
}
async fn stats(&self) -> Result<HashMap<String, String>> {
let mut stats = HashMap::new();
stats.insert("type".to_string(), "moka".to_string());
stats.insert("capacity".to_string(), self.capacity.to_string());
stats.insert("entry_count".to_string(), self.cache.entry_count().to_string());
Ok(stats)
}
}
#[async_trait]
impl CacheWriter for MokaMemoryBackend {
async fn set(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<()> {
let _ = ttl;
self.cache.insert(key.to_string(), value).await;
Ok(())
}
async fn delete(&self, key: &str) -> Result<()> {
self.cache.invalidate(key).await;
Ok(())
}
async fn clear(&self) -> Result<()> {
self.cache.invalidate_all();
Ok(())
}
async fn expire(&self, _key: &str, _ttl: Duration) -> Result<bool> {
Ok(false)
}
}
#[async_trait]
impl CacheConnector for MokaMemoryBackend {
async fn health_check(&self) -> Result<()> {
Ok(())
}
async fn shutdown(&self) {
self.cache.invalidate_all();
}
fn backend_kind(&self) -> BackendKind {
BackendKind::Moka
}
}
impl BackendScore for MokaMemoryBackend {
fn score(&self) -> u8 {
Scores::MOKA
}
fn is_persistent(&self) -> bool {
false
}
fn backend_name(&self) -> &'static str {
"moka"
}
}
#[derive(Default)]
pub struct MokaMemoryBackendBuilder {
capacity: u64,
ttl: Option<Duration>,
time_to_idle: Option<Duration>,
}
impl MokaMemoryBackendBuilder {
pub fn capacity(mut self, capacity: u64) -> Self {
self.capacity = capacity;
self
}
pub fn ttl(mut self, ttl: Duration) -> Self {
self.ttl = Some(ttl);
self
}
pub fn time_to_idle(mut self, ttl: Duration) -> Self {
self.time_to_idle = Some(ttl);
self
}
pub fn build(self) -> MokaMemoryBackend {
let capacity = if self.capacity > 0 {
self.capacity
} else {
10_000 };
let mut builder = moka::future::Cache::builder().max_capacity(capacity);
if let Some(ttl) = self.ttl {
builder = builder.time_to_live(ttl);
}
if let Some(tti) = self.time_to_idle {
builder = builder.time_to_idle(tti);
}
let cache = Arc::new(builder.build());
MokaMemoryBackend { cache, capacity }
}
}
pub fn moka_memory() -> MokaMemoryBackend {
MokaMemoryBackend::new()
}
pub fn moka_memory_with_capacity(capacity: u64) -> MokaMemoryBackend {
MokaMemoryBackend::builder().capacity(capacity).build()
}
pub fn moka_memory_with_capacity_and_ttl(capacity: u64, ttl: Duration) -> MokaMemoryBackend {
MokaMemoryBackend::builder().capacity(capacity).ttl(ttl).build()
}
pub fn default_memory_backend() -> MokaMemoryBackend {
moka_memory()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_moka_backend_builder() {
let backend = MokaMemoryBackend::builder()
.capacity(1000)
.ttl(Duration::from_secs(3600))
.time_to_idle(Duration::from_secs(1800))
.build();
assert_eq!(backend.capacity(), 1000);
}
#[test]
fn test_moka_backend_default() {
let backend = MokaMemoryBackend::default();
assert!(backend.capacity() > 0);
}
#[tokio::test]
async fn test_moka_basic_operations() {
let backend = MokaMemoryBackend::new();
backend.set("key1", b"value1".to_vec(), None).await.unwrap();
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
let result = backend.get("key1").await.unwrap();
assert_eq!(result, Some(b"value1".to_vec()));
let exists = backend.exists("key1").await.unwrap();
assert!(exists);
backend.delete("key1").await.unwrap();
let exists_after = backend.exists("key1").await.unwrap();
assert!(!exists_after);
}
#[test]
fn test_convenience_functions() {
let backend1 = moka_memory();
let backend2 = moka_memory_with_capacity(1000);
let backend3 = moka_memory_with_capacity_and_ttl(1000, Duration::from_secs(3600));
assert!(backend1.capacity() > 0);
assert_eq!(backend2.capacity(), 1000);
assert_eq!(backend3.capacity(), 1000);
}
}