use crate::backend::{CacheBackend, MemoryBackend, RedisBackend, RedisMode, TieredBackend};
use crate::error::Result;
use std::sync::Arc;
pub enum BackendBuilder {
Memory {
capacity: u64,
ttl: Option<std::time::Duration>,
},
Redis {
connection_string: Option<String>,
mode: RedisMode,
},
Tiered {
l1_capacity: u64,
l2_connection_string: Option<String>,
l2_mode: RedisMode,
auto_promote: bool,
},
}
impl BackendBuilder {
pub fn memory() -> Self {
BackendBuilder::Memory {
capacity: 10000,
ttl: None,
}
}
pub fn redis() -> Self {
BackendBuilder::Redis {
connection_string: None,
mode: RedisMode::Standalone,
}
}
pub fn tiered() -> Self {
BackendBuilder::Tiered {
l1_capacity: 10000,
l2_connection_string: None,
l2_mode: RedisMode::Standalone,
auto_promote: true,
}
}
pub fn capacity(mut self, capacity: u64) -> Self {
if let BackendBuilder::Memory { capacity: _c, ttl } = self {
self = BackendBuilder::Memory { capacity, ttl };
}
self
}
pub fn ttl(mut self, ttl: std::time::Duration) -> Self {
if let BackendBuilder::Memory { capacity, ttl: _t } = self {
self = BackendBuilder::Memory {
capacity,
ttl: Some(ttl),
};
}
self
}
pub fn connection_string(mut self, connection_string: &str) -> Self {
match self {
BackendBuilder::Redis { mode, .. } => {
self = BackendBuilder::Redis {
connection_string: Some(connection_string.to_string()),
mode,
};
}
BackendBuilder::Tiered {
l1_capacity,
l2_mode,
auto_promote,
..
} => {
self = BackendBuilder::Tiered {
l1_capacity,
l2_connection_string: Some(connection_string.to_string()),
l2_mode,
auto_promote,
};
}
_ => {}
}
self
}
pub fn mode(mut self, mode: RedisMode) -> Self {
match self {
BackendBuilder::Redis {
connection_string, ..
} => {
self = BackendBuilder::Redis {
connection_string,
mode,
};
}
BackendBuilder::Tiered {
l1_capacity,
l2_connection_string,
auto_promote,
..
} => {
self = BackendBuilder::Tiered {
l1_capacity,
l2_connection_string,
l2_mode: mode,
auto_promote,
};
}
_ => {}
}
self
}
pub fn l1_capacity(mut self, capacity: u64) -> Self {
if let BackendBuilder::Tiered {
l2_connection_string,
l2_mode,
auto_promote,
..
} = self
{
self = BackendBuilder::Tiered {
l1_capacity: capacity,
l2_connection_string,
l2_mode,
auto_promote,
};
}
self
}
pub fn l2_connection_string(mut self, connection_string: &str) -> Self {
if let BackendBuilder::Tiered {
l1_capacity,
l2_mode,
auto_promote,
..
} = self
{
self = BackendBuilder::Tiered {
l1_capacity,
l2_connection_string: Some(connection_string.to_string()),
l2_mode,
auto_promote,
};
}
self
}
pub fn l2_mode(mut self, mode: RedisMode) -> Self {
if let BackendBuilder::Tiered {
l1_capacity,
l2_connection_string,
auto_promote,
..
} = self
{
self = BackendBuilder::Tiered {
l1_capacity,
l2_connection_string,
l2_mode: mode,
auto_promote,
};
}
self
}
pub fn auto_promote(mut self, auto_promote: bool) -> Self {
if let BackendBuilder::Tiered {
l1_capacity,
l2_connection_string,
l2_mode,
..
} = self
{
self = BackendBuilder::Tiered {
l1_capacity,
l2_connection_string,
l2_mode,
auto_promote,
};
}
self
}
pub async fn build(self) -> Result<Arc<dyn CacheBackend>> {
match self {
BackendBuilder::Memory { capacity, ttl } => {
let builder = MemoryBackend::builder().capacity(capacity);
let backend = if let Some(ttl) = ttl {
builder.ttl(ttl).build()
} else {
builder.build()
};
Ok(Arc::new(backend))
}
BackendBuilder::Redis {
connection_string,
mode,
} => {
let connection_string = connection_string.ok_or_else(|| {
crate::error::CacheError::ConfigError(
"Redis connection string is required".to_string(),
)
})?;
let builder = RedisBackend::builder()
.connection_string(&connection_string)
.mode(mode);
let backend = builder.build().await?;
Ok(Arc::new(backend))
}
BackendBuilder::Tiered {
l1_capacity,
l2_connection_string,
l2_mode,
auto_promote,
} => {
let l2_connection_string = l2_connection_string.ok_or_else(|| {
crate::error::CacheError::ConfigError(
"L2 connection string is required".to_string(),
)
})?;
let l1 = MemoryBackend::builder().capacity(l1_capacity).build();
let l2 = RedisBackend::builder()
.connection_string(&l2_connection_string)
.mode(l2_mode)
.build()
.await?;
let backend = TieredBackend::builder()
.l1(l1)
.l2(l2)
.auto_promote(auto_promote)
.build()?;
Ok(Arc::new(backend))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_backend_builder_memory() {
let backend = BackendBuilder::memory()
.capacity(1000)
.build()
.await
.unwrap();
assert!(backend.health_check().await.unwrap());
}
#[tokio::test]
#[ignore] async fn test_backend_builder_redis() {
let backend = BackendBuilder::redis()
.connection_string("redis://localhost:6379")
.mode(RedisMode::Standalone)
.build()
.await
.unwrap();
assert!(backend.health_check().await.unwrap());
}
#[tokio::test]
async fn test_backend_builder_tiered() {
let l1 = MemoryBackend::new();
let l2 = MemoryBackend::new();
let backend = TieredBackend::builder()
.l1(l1)
.l2(l2)
.auto_promote(true)
.build()
.unwrap();
assert!(backend.health_check().await.unwrap());
}
}