use crate::backend::interface::CacheBackend;
use crate::backend::memory::moka::MokaMemoryBackend;
use crate::cache::Cache;
use crate::error::Result;
use crate::traits::CacheKey;
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::Duration;
pub struct CacheBuilder<K, V> {
backends: Vec<Arc<dyn CacheBackend>>,
ttl: Option<Duration>,
tti: Option<Duration>,
capacity: Option<u64>,
_phantom: PhantomData<(K, V)>,
}
impl<K, V> Default for CacheBuilder<K, V> {
fn default() -> Self {
Self {
backends: Vec::new(),
ttl: None,
tti: None,
capacity: None,
_phantom: PhantomData,
}
}
}
impl<K, V> CacheBuilder<K, V>
where
K: CacheKey,
V: serde::Serialize + for<'de> serde::Deserialize<'de>,
{
pub fn backend_arc(mut self, backend: Arc<dyn CacheBackend>) -> Self {
self.backends.push(backend);
self
}
pub fn ttl(mut self, ttl: Duration) -> Self {
self.ttl = Some(ttl);
self
}
pub fn tti(mut self, tti: Duration) -> Self {
self.tti = Some(tti);
self
}
pub fn capacity(mut self, capacity: u64) -> Self {
self.capacity = Some(capacity);
self
}
pub async fn build(self) -> Result<Cache<K, V>> {
let backend = if self.backends.is_empty() {
let capacity = self.capacity.unwrap_or(10000);
let mut builder = MokaMemoryBackend::builder().capacity(capacity);
if let Some(ttl) = self.ttl {
builder = builder.ttl(ttl);
}
if let Some(tti) = self.tti {
builder = builder.time_to_idle(tti);
}
Arc::new(builder.build()) as Arc<dyn CacheBackend>
} else {
self.backends[0].clone()
};
Ok(Cache::new_with_backend(backend))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_default() {
let builder: CacheBuilder<String, String> = CacheBuilder::default();
assert!(builder.backends.is_empty());
assert!(builder.ttl.is_none());
}
#[tokio::test]
async fn test_builder_empty() {
let cache: Cache<String, i32> = Cache::builder().build().await.unwrap();
cache.set(&"key".to_string(), &42).await.unwrap();
assert_eq!(cache.get(&"key".to_string()).await.unwrap().unwrap(), 42);
}
#[tokio::test]
async fn test_builder_single_backend() {
let backend = MokaMemoryBackend::builder().capacity(100).build();
let cache: Cache<String, i32> = Cache::builder().backend_arc(Arc::new(backend)).build().await.unwrap();
cache.set(&"key".to_string(), &42).await.unwrap();
assert_eq!(cache.get(&"key".to_string()).await.unwrap().unwrap(), 42);
}
#[test]
fn test_builder_ttl() {
let builder: CacheBuilder<String, String> = CacheBuilder::default().ttl(Duration::from_secs(60));
assert_eq!(builder.ttl, Some(Duration::from_secs(60)));
}
#[test]
fn test_builder_ttl_zero() {
let builder: CacheBuilder<String, String> = CacheBuilder::default().ttl(Duration::from_secs(0));
assert_eq!(builder.ttl, Some(Duration::from_secs(0)));
}
#[test]
fn test_builder_ttl_chained() {
let builder: CacheBuilder<String, String> = CacheBuilder::default().ttl(Duration::from_secs(30)).capacity(100);
assert_eq!(builder.ttl, Some(Duration::from_secs(30)));
assert_eq!(builder.capacity, Some(100));
}
#[test]
fn test_builder_tti() {
let builder: CacheBuilder<String, String> = CacheBuilder::default().tti(Duration::from_secs(120));
assert_eq!(builder.tti, Some(Duration::from_secs(120)));
}
#[test]
fn test_builder_tti_zero() {
let builder: CacheBuilder<String, String> = CacheBuilder::default().tti(Duration::from_secs(0));
assert_eq!(builder.tti, Some(Duration::from_secs(0)));
}
#[test]
fn test_builder_tti_chained() {
let builder: CacheBuilder<String, String> = CacheBuilder::default()
.tti(Duration::from_secs(45))
.ttl(Duration::from_secs(300));
assert_eq!(builder.tti, Some(Duration::from_secs(45)));
assert_eq!(builder.ttl, Some(Duration::from_secs(300)));
}
#[test]
fn test_builder_capacity() {
let builder: CacheBuilder<String, String> = CacheBuilder::default().capacity(10000);
assert_eq!(builder.capacity, Some(10000));
}
#[test]
fn test_builder_capacity_zero() {
let builder: CacheBuilder<String, String> = CacheBuilder::default().capacity(0);
assert_eq!(builder.capacity, Some(0));
}
#[test]
fn test_builder_capacity_chained() {
let builder: CacheBuilder<String, String> = CacheBuilder::default()
.capacity(500)
.ttl(Duration::from_secs(60))
.tti(Duration::from_secs(30));
assert_eq!(builder.capacity, Some(500));
assert_eq!(builder.ttl, Some(Duration::from_secs(60)));
assert_eq!(builder.tti, Some(Duration::from_secs(30)));
}
#[test]
fn test_builder_backend_arc() {
let backend = MokaMemoryBackend::builder().capacity(100).build();
let builder: CacheBuilder<String, String> = CacheBuilder::default().backend_arc(Arc::new(backend));
assert_eq!(builder.backends.len(), 1);
}
#[test]
fn test_builder_backend_arc_multiple() {
let backend1 = MokaMemoryBackend::builder().capacity(100).build();
let backend2 = MokaMemoryBackend::builder().capacity(200).build();
let builder: CacheBuilder<String, String> = CacheBuilder::default()
.backend_arc(Arc::new(backend1))
.backend_arc(Arc::new(backend2));
assert_eq!(builder.backends.len(), 2);
}
#[tokio::test]
async fn test_builder_build_with_ttl() {
let cache: Cache<String, i32> = Cache::builder().ttl(Duration::from_secs(60)).build().await.unwrap();
cache.set(&"key".to_string(), &42).await.unwrap();
assert_eq!(cache.get(&"key".to_string()).await.unwrap().unwrap(), 42);
}
#[tokio::test]
async fn test_builder_build_with_tti() {
let cache: Cache<String, i32> = Cache::builder().tti(Duration::from_secs(60)).build().await.unwrap();
cache.set(&"key".to_string(), &42).await.unwrap();
assert_eq!(cache.get(&"key".to_string()).await.unwrap().unwrap(), 42);
}
#[tokio::test]
async fn test_builder_build_with_capacity() {
let cache: Cache<String, i32> = Cache::builder().capacity(100).build().await.unwrap();
cache.set(&"key".to_string(), &42).await.unwrap();
assert_eq!(cache.get(&"key".to_string()).await.unwrap().unwrap(), 42);
}
#[tokio::test]
async fn test_builder_build_with_ttl_and_tti() {
let cache: Cache<String, i32> = Cache::builder()
.ttl(Duration::from_secs(60))
.tti(Duration::from_secs(30))
.capacity(100)
.build()
.await
.unwrap();
cache.set(&"key".to_string(), &42).await.unwrap();
assert_eq!(cache.get(&"key".to_string()).await.unwrap().unwrap(), 42);
}
#[test]
fn test_builder_default_capacity_none() {
let builder: CacheBuilder<String, String> = CacheBuilder::default();
assert!(builder.capacity.is_none());
}
#[test]
fn test_builder_default_tti_none() {
let builder: CacheBuilder<String, String> = CacheBuilder::default();
assert!(builder.tti.is_none());
}
#[test]
fn test_builder_default_backends_empty() {
let builder: CacheBuilder<String, String> = CacheBuilder::default();
assert!(builder.backends.is_empty());
}
#[test]
fn test_builder_full_chain() {
let backend = MokaMemoryBackend::builder().capacity(100).build();
let builder: CacheBuilder<String, String> = CacheBuilder::default()
.ttl(Duration::from_secs(60))
.tti(Duration::from_secs(30))
.capacity(1000)
.backend_arc(Arc::new(backend));
assert_eq!(builder.ttl, Some(Duration::from_secs(60)));
assert_eq!(builder.tti, Some(Duration::from_secs(30)));
assert_eq!(builder.capacity, Some(1000));
assert_eq!(builder.backends.len(), 1);
}
}