use async_trait::async_trait;
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HealthStatus {
Healthy,
Unhealthy(String),
Degraded(String),
}
#[derive(Debug, Clone)]
pub struct ScanResult {
pub keys: Vec<String>,
pub cursor: u64,
}
pub struct ScanIterator<'a> {
strategy: &'a dyn L2BackendStrategy,
pattern: String,
cursor: u64,
count: usize,
}
impl<'a> ScanIterator<'a> {
pub fn new(strategy: &'a dyn L2BackendStrategy, pattern: &str, count: usize) -> Self {
Self {
strategy,
pattern: pattern.to_string(),
cursor: 0,
count,
}
}
}
#[async_trait]
impl<'a> Iterator for ScanIterator<'a> {
type Item = Result<String, crate::error::CacheError>;
fn next(&mut self) -> Option<Self::Item> {
if self.cursor == u64::MAX {
return None;
}
let result = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
self.strategy
.scan(&self.pattern, self.count, self.cursor)
.await
})
});
match result {
Ok(scan_result) => {
self.cursor = scan_result.cursor;
if scan_result.cursor == 0 {
self.cursor = u64::MAX; }
scan_result.keys.into_iter().map(Ok).next()
}
Err(e) => Some(Err(e)),
}
}
}
#[async_trait]
pub trait L2BackendStrategy: Send + Sync {
fn name(&self) -> &str;
fn is_connected(&self) -> bool;
async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, crate::error::CacheError>;
async fn set(
&self,
key: &str,
value: &[u8],
ttl: Option<u64>,
) -> Result<(), crate::error::CacheError>;
async fn delete(&self, key: &str) -> Result<bool, crate::error::CacheError>;
async fn exists(&self, key: &str) -> Result<bool, crate::error::CacheError>;
async fn expire(&self, key: &str, ttl: u64) -> Result<bool, crate::error::CacheError>;
async fn ttl(&self, key: &str) -> Result<Option<i64>, crate::error::CacheError>;
async fn get_with_version(
&self,
key: &str,
) -> Result<Option<(Vec<u8>, u64)>, crate::error::CacheError>;
async fn compare_and_set(
&self,
key: &str,
value: &[u8],
expected_version: u64,
new_version: u64,
ttl: Option<u64>,
) -> Result<bool, crate::error::CacheError>;
async fn lock(&self, key: &str, ttl: u64) -> Result<Option<String>, crate::error::CacheError>;
async fn unlock(&self, key: &str, value: &str) -> Result<bool, crate::error::CacheError>;
async fn mget(
&self,
keys: &[&str],
) -> Result<std::collections::HashMap<String, Vec<u8>>, crate::error::CacheError>;
async fn mset(
&self,
items: &[(&str, &[u8])],
ttl: Option<u64>,
) -> Result<(), crate::error::CacheError>;
async fn scan(
&self,
pattern: &str,
count: usize,
cursor: u64,
) -> Result<ScanResult, crate::error::CacheError>;
async fn scan_keys(
&self,
pattern: &str,
limit: usize,
) -> Result<Vec<String>, crate::error::CacheError>;
async fn ping(&self) -> Result<(), crate::error::CacheError>;
async fn health_check(&self) -> Result<HealthStatus, crate::error::CacheError>;
fn command_timeout(&self) -> Duration;
async fn close(&self) -> Result<(), crate::error::CacheError>;
}