use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::sync::Mutex;
pub fn canonicalize_cache_key(key: &str) -> String {
key.trim().to_lowercase()
}
fn matches_pattern(pattern: &str, text: &str) -> bool {
let p: Vec<char> = pattern.chars().collect();
let t: Vec<char> = text.chars().collect();
let mut pi = 0usize;
let mut ti = 0usize;
let mut star_pi: Option<usize> = None;
let mut star_ti = 0usize;
while ti < t.len() {
if pi < p.len() && (p[pi] == t[ti] || p[pi] == '?') {
pi += 1;
ti += 1;
} else if pi < p.len() && p[pi] == '*' {
star_pi = Some(pi);
star_ti = ti;
pi += 1;
} else if let Some(spi) = star_pi {
pi = spi + 1;
star_ti += 1;
ti = star_ti;
} else {
return false;
}
}
while pi < p.len() && p[pi] == '*' {
pi += 1;
}
pi == p.len()
}
pub trait SyncCache: Send + Sync {
fn get(&self, key: &str) -> Option<Vec<u8>>;
fn get_many(&self, keys: &[&str]) -> HashMap<String, Vec<u8>> {
keys.iter()
.filter_map(|&key| self.get(key).map(|v| (key.to_string(), v)))
.collect()
}
fn set(&self, key: &str, value: Vec<u8>);
fn set_many(&self, items: &[(String, Vec<u8>)]) {
for (key, value) in items {
self.set(key, value.clone());
}
}
fn delete(&self, key: &str) -> bool;
fn delete_many(&self, keys: &[&str]) -> usize {
keys.iter().filter(|&&key| self.delete(key)).count()
}
fn contains(&self, key: &str) -> bool;
fn clear(&self);
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
fn invalidate(&self, pattern: &str) -> usize {
let keys = self.find_keys_by_pattern(pattern);
self.delete_many(&keys.iter().map(|s| s.as_str()).collect::<Vec<_>>())
}
fn find_keys_by_pattern(&self, pattern: &str) -> Vec<String>;
fn get_stats(&self) -> HashMap<String, u64> {
HashMap::new()
}
}
pub type SharedCache = Arc<dyn SyncCache>;
pub struct OxcacheSyncCache {
backend: oxcache::backend::DashMapMemoryBackend,
key_index: Mutex<HashSet<String>>,
}
impl OxcacheSyncCache {
pub fn new() -> Self {
Self {
backend: oxcache::backend::DashMapMemoryBackend::new(),
key_index: Mutex::new(HashSet::new()),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
backend: oxcache::backend::DashMapMemoryBackend::builder()
.capacity(capacity)
.build(),
key_index: Mutex::new(HashSet::with_capacity(capacity)),
}
}
pub fn inner(&self) -> &oxcache::backend::DashMapMemoryBackend {
&self.backend
}
}
impl Default for OxcacheSyncCache {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for OxcacheSyncCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let len = self.key_index.lock().map(|idx| idx.len()).unwrap_or(0);
f.debug_struct("OxcacheSyncCache")
.field("backend", &"DashMapMemoryBackend")
.field("key_count", &len)
.finish()
}
}
impl SyncCache for OxcacheSyncCache {
fn get(&self, key: &str) -> Option<Vec<u8>> {
use oxcache::backend::SyncCacheReader;
self.backend.get(key).ok().flatten()
}
fn set(&self, key: &str, value: Vec<u8>) {
use oxcache::backend::SyncCacheWriter;
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => {
log::warn!(
"cache key_index poisoned; set falling back to backend-only for key={:?}",
key
);
if let Err(e) = self.backend.set(key, value, None) {
log::warn!("cache backend set failed for key={:?}: {}", key, e);
}
return;
}
};
if let Err(e) = self.backend.set(key, value, None) {
log::warn!("cache backend set failed for key={:?}: {}", key, e);
return;
}
idx.insert(key.to_string());
}
fn delete(&self, key: &str) -> bool {
use oxcache::backend::{SyncCacheReader, SyncCacheWriter};
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => {
log::warn!(
"cache key_index poisoned; delete falling back to backend-only for key={:?}",
key
);
let existed = self.backend.exists(key).unwrap_or(false);
if existed {
if let Err(e) = self.backend.delete(key) {
log::warn!("cache backend delete failed for key={:?}: {}", key, e);
}
}
return existed;
}
};
let existed = self.backend.exists(key).unwrap_or(false);
if existed {
if let Err(e) = self.backend.delete(key) {
log::warn!("cache backend delete failed for key={:?}: {}", key, e);
return existed;
}
idx.remove(key);
}
existed
}
fn contains(&self, key: &str) -> bool {
use oxcache::backend::SyncCacheReader;
self.backend.exists(key).unwrap_or(false)
}
fn clear(&self) {
use oxcache::backend::SyncCacheWriter;
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => {
log::warn!("cache key_index poisoned; clear falling back to backend-only");
if let Err(e) = self.backend.clear() {
log::warn!("cache backend clear failed: {}", e);
}
return;
}
};
if let Err(e) = self.backend.clear() {
log::warn!("cache backend clear failed: {}", e);
return;
}
idx.clear();
}
fn len(&self) -> usize {
use oxcache::backend::SyncCacheReader;
self.backend.len().unwrap_or(0) as usize
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn find_keys_by_pattern(&self, pattern: &str) -> Vec<String> {
use oxcache::backend::SyncCacheReader;
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => return Vec::new(),
};
let mut result = Vec::new();
let mut stale_keys = Vec::new();
for k in idx.iter() {
if !matches_pattern(pattern, k) {
continue;
}
if self.backend.exists(k).unwrap_or(false) {
result.push(k.clone());
} else {
stale_keys.push(k.clone());
}
}
for k in stale_keys {
idx.remove(&k);
}
result
}
fn get_many(&self, keys: &[&str]) -> HashMap<String, Vec<u8>> {
use oxcache::backend::SyncCacheReader;
let mut results = HashMap::with_capacity(keys.len());
for &key in keys {
if let Ok(Some(value)) = self.backend.get(key) {
results.insert(key.to_string(), value);
}
}
results
}
fn set_many(&self, items: &[(String, Vec<u8>)]) {
use oxcache::backend::SyncCacheWriter;
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => {
log::warn!(
"cache key_index poisoned; set_many falling back to backend-only for {} items",
items.len()
);
for (key, value) in items {
if let Err(e) = self.backend.set(key, value.clone(), None) {
log::warn!("cache backend set failed for key={:?}: {}", key, e);
}
}
return;
}
};
let mut succeeded: Vec<&String> = Vec::with_capacity(items.len());
for (key, value) in items {
if let Err(e) = self.backend.set(key, value.clone(), None) {
log::warn!("cache backend set failed for key={:?}: {}", key, e);
} else {
succeeded.push(key);
}
}
for key in succeeded {
idx.insert(key.clone());
}
}
fn delete_many(&self, keys: &[&str]) -> usize {
use oxcache::backend::{SyncCacheReader, SyncCacheWriter};
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => {
log::warn!(
"cache key_index poisoned; delete_many falling back to backend-only for {} keys",
keys.len()
);
let mut deleted = 0usize;
for &key in keys {
let existed = self.backend.exists(key).unwrap_or(false);
if existed {
if let Err(e) = self.backend.delete(key) {
log::warn!("cache backend delete failed for key={:?}: {}", key, e);
} else {
deleted += 1;
}
}
}
return deleted;
}
};
let mut deleted = 0usize;
for &key in keys {
let existed = self.backend.exists(key).unwrap_or(false);
if existed {
if let Err(e) = self.backend.delete(key) {
log::warn!("cache backend delete failed for key={:?}: {}", key, e);
} else {
idx.remove(key);
deleted += 1;
}
}
}
deleted
}
fn get_stats(&self) -> HashMap<String, u64> {
use oxcache::backend::SyncCacheReader;
let mut stats = HashMap::new();
let len = self.backend.len().unwrap_or(0);
stats.insert("total_keys".to_string(), len);
stats.insert(
"capacity".to_string(),
SyncCacheReader::capacity(&self.backend).unwrap_or(0),
);
if let Ok(backend_stats) = self.backend.stats() {
for (k, v) in backend_stats {
if let Ok(n) = v.parse::<u64>() {
stats.entry(k).or_insert(n);
} else if let Ok(f) = v.parse::<f64>() {
let lower = k.to_lowercase();
let converted = if lower.contains("rate")
|| lower.contains("ratio")
|| lower.contains("pct")
{
(f * 100.0).round() as u64
} else {
f.round() as u64
};
log::debug!(
"cache stat {:?} parsed as f64={} converted to u64={}",
k,
f,
converted
);
stats.entry(k).or_insert(converted);
} else {
log::warn!(
"cache stat {:?} value {:?} could not be parsed as u64 or f64; dropped",
k,
v
);
}
}
}
stats
}
}
pub type DashMapCache = OxcacheSyncCache;
pub use oxcache::cache::Cache;
pub use oxcache::traits::CacheKey;
#[cfg(test)]
mod tests {
use super::*;
use crate::cache::DashMapCache;
#[test]
fn test_canonicalize_cache_key_trims_whitespace() {
assert_eq!(canonicalize_cache_key(" user:123 "), "user:123");
assert_eq!(canonicalize_cache_key("key"), "key");
assert_eq!(canonicalize_cache_key("\tkey\n"), "key");
}
#[test]
fn test_canonicalize_cache_key_lowercase() {
assert_eq!(canonicalize_cache_key("USER:123"), "user:123");
assert_eq!(canonicalize_cache_key("MixedCase"), "mixedcase");
assert_eq!(canonicalize_cache_key("USER:ABC"), "user:abc");
}
#[test]
fn test_canonicalize_cache_key_combined() {
assert_eq!(canonicalize_cache_key(" USER:123 "), "user:123");
assert_eq!(canonicalize_cache_key("\tMIXED_CASE\n"), "mixed_case");
}
#[test]
fn test_matches_pattern_exact() {
assert!(matches_pattern("user:1", "user:1"));
assert!(!matches_pattern("user:1", "user:2"));
}
#[test]
fn test_matches_pattern_prefix_wildcard() {
assert!(matches_pattern("user:*", "user:1"));
assert!(matches_pattern("user:*", "user:2"));
assert!(matches_pattern("user:*", "user:"));
assert!(!matches_pattern("user:*", "session:1"));
}
#[test]
fn test_matches_pattern_suffix_wildcard() {
assert!(matches_pattern("*:1", "user:1"));
assert!(matches_pattern("*:1", "admin:1"));
assert!(!matches_pattern("*:1", "user:2"));
}
#[test]
fn test_matches_pattern_middle_wildcard() {
assert!(matches_pattern("*session*", "my_session_data"));
assert!(matches_pattern("*session*", "session"));
assert!(!matches_pattern("*session*", "user:1"));
}
#[test]
fn test_matches_pattern_single_char_wildcard() {
assert!(matches_pattern("user:?", "user:1"));
assert!(matches_pattern("user:?", "user:a"));
assert!(!matches_pattern("user:?", "user:12"));
}
#[test]
fn test_matches_pattern_combined_wildcards() {
assert!(matches_pattern("?ser:*", "user:1"));
assert!(matches_pattern("?ser:*", "aser:xyz"));
assert!(!matches_pattern("?ser:*", "xser1:"));
}
#[test]
fn test_matches_pattern_empty_pattern_and_text() {
assert!(matches_pattern("", ""));
assert!(matches_pattern("*", ""));
assert!(!matches_pattern("", "a"));
assert!(matches_pattern("*", "anything"));
}
#[test]
fn test_synccache_trait_get_set() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
cache.set("test_key", b"test_value".to_vec());
assert_eq!(cache.get("test_key"), Some(b"test_value".to_vec()));
assert!(cache.contains("test_key"));
}
#[test]
fn test_synccache_trait_delete() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
cache.set("key1", b"value1".to_vec());
assert!(cache.delete("key1"));
assert!(!cache.contains("key1"));
assert_eq!(cache.get("key1"), None);
}
#[test]
fn test_synccache_trait_clear() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
cache.set("key1", b"v1".to_vec());
cache.set("key2", b"v2".to_vec());
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_synccache_trait_len_and_is_empty() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
cache.set("key1", b"v1".to_vec());
cache.set("key2", b"v2".to_vec());
assert!(!cache.is_empty());
assert_eq!(cache.len(), 2);
}
#[test]
fn test_synccache_trait_get_many() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
cache.set("key1", b"v1".to_vec());
cache.set("key2", b"v2".to_vec());
cache.set("key3", b"v3".to_vec());
let results = cache.get_many(&["key1", "key2", "nonexistent"]);
assert_eq!(results.len(), 2);
assert_eq!(results.get("key1"), Some(&b"v1".to_vec()));
}
#[test]
fn test_synccache_trait_set_many() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
let items = vec![
("k1".to_string(), b"v1".to_vec()),
("k2".to_string(), b"v2".to_vec()),
];
cache.set_many(&items);
assert_eq!(cache.len(), 2);
assert_eq!(cache.get("k1"), Some(b"v1".to_vec()));
assert_eq!(cache.get("k2"), Some(b"v2".to_vec()));
}
#[test]
fn test_synccache_trait_delete_many() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
cache.set("k1", b"v1".to_vec());
cache.set("k2", b"v2".to_vec());
cache.set("k3", b"v3".to_vec());
let deleted = cache.delete_many(&["k1", "k3", "nonexistent"]);
assert_eq!(deleted, 2);
assert_eq!(cache.len(), 1);
}
#[test]
fn test_synccache_trait_invalidate() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
cache.set("user:1", b"v1".to_vec());
cache.set("user:2", b"v2".to_vec());
cache.set("session:1", b"s1".to_vec());
let deleted = cache.invalidate("user:*");
assert_eq!(deleted, 2);
assert_eq!(cache.len(), 1);
assert!(cache.contains("session:1"));
}
#[test]
fn test_synccache_trait_find_keys_by_pattern() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
cache.set("user:1", b"v1".to_vec());
cache.set("user:2", b"v2".to_vec());
cache.set("admin:1", b"a1".to_vec());
let keys = cache.find_keys_by_pattern("user:*");
assert_eq!(keys.len(), 2);
assert!(keys.contains(&"user:1".to_string()));
assert!(keys.contains(&"user:2".to_string()));
}
#[test]
fn test_synccache_trait_find_keys_by_pattern_suffix() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
cache.set("user:1", b"v1".to_vec());
cache.set("admin:1", b"a1".to_vec());
cache.set("user:2", b"v2".to_vec());
let keys = cache.find_keys_by_pattern("*:1");
assert_eq!(keys.len(), 2);
assert!(keys.contains(&"user:1".to_string()));
assert!(keys.contains(&"admin:1".to_string()));
}
#[test]
fn test_synccache_trait_find_keys_by_pattern_no_match() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
cache.set("user:1", b"v1".to_vec());
let keys = cache.find_keys_by_pattern("session:*");
assert!(keys.is_empty());
}
#[test]
fn test_synccache_trait_get_stats() {
let cache: Box<dyn SyncCache> = Box::new(DashMapCache::new());
cache.set("k1", b"v1".to_vec());
cache.set("k2", b"v2".to_vec());
let stats = cache.get_stats();
assert!(stats.contains_key("total_keys"));
assert_eq!(stats.get("total_keys"), Some(&2));
assert!(stats.contains_key("capacity"));
}
#[test]
fn test_shared_cache_type_alias() {
let cache: SharedCache = Arc::new(DashMapCache::new());
cache.set("key", b"value".to_vec());
assert!(cache.contains("key"));
}
#[test]
fn test_oxcache_sync_cache_with_capacity() {
let cache = OxcacheSyncCache::with_capacity(100);
cache.set("k1", b"v1".to_vec());
assert_eq!(cache.get("k1"), Some(b"v1".to_vec()));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_oxcache_sync_cache_default() {
let cache = OxcacheSyncCache::default();
assert!(cache.is_empty());
}
#[test]
fn test_oxcache_sync_cache_inner() {
let cache = OxcacheSyncCache::new();
let _backend: &oxcache::backend::DashMapMemoryBackend = cache.inner();
}
#[test]
fn test_oxcache_sync_cache_debug() {
let cache = OxcacheSyncCache::new();
let debug_str = format!("{:?}", cache);
assert!(debug_str.contains("OxcacheSyncCache"));
}
#[test]
fn test_oxcache_sync_cache_delete_nonexistent_returns_false() {
let cache = OxcacheSyncCache::new();
assert!(!cache.delete("nonexistent"));
}
struct MinimalCache {
data: std::sync::Mutex<HashMap<String, Vec<u8>>>,
}
impl MinimalCache {
fn new() -> Self {
Self {
data: std::sync::Mutex::new(HashMap::new()),
}
}
}
impl SyncCache for MinimalCache {
fn get(&self, key: &str) -> Option<Vec<u8>> {
self.data.lock().unwrap().get(key).cloned()
}
fn set(&self, key: &str, value: Vec<u8>) {
self.data.lock().unwrap().insert(key.to_string(), value);
}
fn delete(&self, key: &str) -> bool {
self.data.lock().unwrap().remove(key).is_some()
}
fn contains(&self, key: &str) -> bool {
self.data.lock().unwrap().contains_key(key)
}
fn clear(&self) {
self.data.lock().unwrap().clear();
}
fn len(&self) -> usize {
self.data.lock().unwrap().len()
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn find_keys_by_pattern(&self, pattern: &str) -> Vec<String> {
self.data
.lock()
.unwrap()
.keys()
.filter(|k| matches_pattern(pattern, k))
.cloned()
.collect()
}
}
#[test]
fn test_default_get_many_implementation() {
let cache = MinimalCache::new();
cache.set("key1", b"v1".to_vec());
cache.set("key2", b"v2".to_vec());
let results = cache.get_many(&["key1", "key2", "missing"]);
assert_eq!(results.len(), 2);
assert_eq!(results.get("key1"), Some(&b"v1".to_vec()));
assert_eq!(results.get("key2"), Some(&b"v2".to_vec()));
assert!(!results.contains_key("missing"));
}
#[test]
fn test_default_get_many_empty() {
let cache = MinimalCache::new();
let results = cache.get_many(&[]);
assert!(results.is_empty());
}
#[test]
fn test_default_set_many_implementation() {
let cache = MinimalCache::new();
let items = vec![
("k1".to_string(), b"v1".to_vec()),
("k2".to_string(), b"v2".to_vec()),
("k3".to_string(), b"v3".to_vec()),
];
cache.set_many(&items);
assert_eq!(cache.len(), 3);
assert_eq!(cache.get("k1"), Some(b"v1".to_vec()));
assert_eq!(cache.get("k2"), Some(b"v2".to_vec()));
assert_eq!(cache.get("k3"), Some(b"v3".to_vec()));
}
#[test]
fn test_default_set_many_empty() {
let cache = MinimalCache::new();
cache.set_many(&[]);
assert!(cache.is_empty());
}
#[test]
fn test_default_delete_many_implementation() {
let cache = MinimalCache::new();
cache.set("k1", b"v1".to_vec());
cache.set("k2", b"v2".to_vec());
cache.set("k3", b"v3".to_vec());
let deleted = cache.delete_many(&["k1", "k3", "missing"]);
assert_eq!(deleted, 2);
assert_eq!(cache.len(), 1);
assert!(cache.contains("k2"));
}
#[test]
fn test_default_delete_many_empty() {
let cache = MinimalCache::new();
let deleted = cache.delete_many(&[]);
assert_eq!(deleted, 0);
}
#[test]
fn test_default_get_stats_implementation() {
let cache = MinimalCache::new();
cache.set("k1", b"v1".to_vec());
cache.set("k2", b"v2".to_vec());
let stats = cache.get_stats();
assert!(stats.is_empty());
}
#[test]
fn test_default_invalidate_implementation() {
let cache = MinimalCache::new();
cache.set("user:1", b"v1".to_vec());
cache.set("user:2", b"v2".to_vec());
cache.set("session:1", b"s1".to_vec());
let deleted = cache.invalidate("user:*");
assert_eq!(deleted, 2);
assert_eq!(cache.len(), 1);
assert!(cache.contains("session:1"));
}
}