use crate::InklogError;
use async_trait::async_trait;
#[async_trait]
pub trait Cache: Send + Sync {
async fn get(&self, key: &str) -> Result<Option<String>, InklogError>;
async fn set(&self, key: &str, value: String) -> Result<(), InklogError>;
async fn delete(&self, key: &str) -> Result<bool, InklogError>;
async fn exists(&self, key: &str) -> Result<bool, InklogError>;
}
use oxcache::Cache as OxCache;
use std::collections::HashMap;
use std::sync::RwLock;
pub struct OxCacheAdapter {
inner: OxCache<String, String>,
}
impl OxCacheAdapter {
pub fn new() -> Result<Self, InklogError> {
let cache = OxCache::new();
Ok(Self { inner: cache })
}
pub fn builder() -> OxCacheAdapterBuilder {
OxCacheAdapterBuilder::default()
}
}
#[async_trait]
impl Cache for OxCacheAdapter {
async fn get(&self, key: &str) -> Result<Option<String>, InklogError> {
self.inner.get(&key.to_string()).await.map_err(|e| {
InklogError::CacheError(format!("Failed to get cache key '{}': {}", key, e))
})
}
async fn set(&self, key: &str, value: String) -> Result<(), InklogError> {
self.inner.set(&key.to_string(), &value).await.map_err(|e| {
InklogError::CacheError(format!("Failed to set cache key '{}': {}", key, e))
})
}
async fn delete(&self, key: &str) -> Result<bool, InklogError> {
let exists = self.exists(key).await?;
if !exists {
return Ok(false);
}
self.inner.delete(&key.to_string()).await.map_err(|e| {
InklogError::CacheError(format!("Failed to delete cache key '{}': {}", key, e))
})?;
Ok(true)
}
async fn exists(&self, key: &str) -> Result<bool, InklogError> {
self.inner.exists(&key.to_string()).await.map_err(|e| {
InklogError::CacheError(format!(
"Failed to check existence of cache key '{}': {}",
key, e
))
})
}
}
#[derive(Default)]
pub struct OxCacheAdapterBuilder {
ttl: Option<std::time::Duration>,
capacity: Option<u64>,
}
impl OxCacheAdapterBuilder {
pub fn ttl(mut self, ttl: std::time::Duration) -> Self {
self.ttl = Some(ttl);
self
}
pub fn capacity(mut self, capacity: u64) -> Self {
self.capacity = Some(capacity);
self
}
pub async fn build(self) -> Result<OxCacheAdapter, InklogError> {
let mut builder = OxCache::builder();
if let Some(ttl) = self.ttl {
builder = builder.ttl(ttl);
}
if let Some(capacity) = self.capacity {
builder = builder.capacity(capacity);
}
let cache = builder
.build()
.await
.map_err(|e| InklogError::CacheError(format!("Failed to build oxcache: {}", e)))?;
Ok(OxCacheAdapter { inner: cache })
}
}
pub struct MockCache {
storage: RwLock<HashMap<String, String>>,
delay_ms: u64,
}
impl MockCache {
pub fn new() -> Self {
Self {
storage: RwLock::new(HashMap::new()),
delay_ms: 0,
}
}
pub fn with_delay(ms: u64) -> Self {
Self {
storage: RwLock::new(HashMap::new()),
delay_ms: ms,
}
}
}
impl Default for MockCache {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Cache for MockCache {
async fn get(&self, key: &str) -> Result<Option<String>, InklogError> {
if self.delay_ms > 0 {
tokio::time::sleep(tokio::time::Duration::from_millis(self.delay_ms)).await;
}
let storage = self.storage.read().unwrap();
Ok(storage.get(key).cloned())
}
async fn set(&self, key: &str, value: String) -> Result<(), InklogError> {
if self.delay_ms > 0 {
tokio::time::sleep(tokio::time::Duration::from_millis(self.delay_ms)).await;
}
let mut storage = self.storage.write().unwrap();
storage.insert(key.to_string(), value);
Ok(())
}
async fn delete(&self, key: &str) -> Result<bool, InklogError> {
if self.delay_ms > 0 {
tokio::time::sleep(tokio::time::Duration::from_millis(self.delay_ms)).await;
}
let mut storage = self.storage.write().unwrap();
Ok(storage.remove(key).is_some())
}
async fn exists(&self, key: &str) -> Result<bool, InklogError> {
if self.delay_ms > 0 {
tokio::time::sleep(tokio::time::Duration::from_millis(self.delay_ms)).await;
}
let storage = self.storage.read().unwrap();
Ok(storage.contains_key(key))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_oxcache_adapter_basic_operations() {
let cache = OxCacheAdapter::new().expect("Failed to create cache");
cache
.set("key1", "value1".to_string())
.await
.expect("Failed to set");
let value = cache.get("key1").await.expect("Failed to get");
assert_eq!(value, Some("value1".to_string()));
assert!(cache.exists("key1").await.expect("exists failed"));
assert!(!cache.exists("nonexistent").await.expect("exists failed"));
assert!(cache.delete("key1").await.expect("delete failed"));
assert!(!cache.exists("key1").await.expect("exists failed"));
assert!(
!cache.delete("key1").await.expect("delete failed"),
"再次删除返回 false"
);
}
#[tokio::test]
async fn test_oxcache_adapter_get_nonexistent() {
let cache = OxCacheAdapter::new().expect("Failed to create cache");
let value = cache.get("nonexistent_key").await.expect("Failed to get");
assert_eq!(value, None);
}
#[tokio::test]
async fn test_oxcache_adapter_overwrite() {
let cache = OxCacheAdapter::new().expect("Failed to create cache");
cache
.set("key", "value1".to_string())
.await
.expect("Failed to set");
cache
.set("key", "value2".to_string())
.await
.expect("Failed to set");
let value = cache.get("key").await.expect("Failed to get");
assert_eq!(value, Some("value2".to_string()));
}
#[tokio::test]
async fn test_oxcache_adapter_builder_default() {
let cache = OxCacheAdapter::builder()
.build()
.await
.expect("Failed to build default cache");
cache
.set("bkey", "bval".to_string())
.await
.expect("Failed to set");
assert_eq!(
cache.get("bkey").await.expect("Failed to get"),
Some("bval".to_string())
);
}
#[tokio::test]
async fn test_oxcache_adapter_builder_with_ttl_and_capacity() {
let cache = OxCacheAdapter::builder()
.ttl(std::time::Duration::from_secs(60))
.capacity(100)
.build()
.await
.expect("Failed to build configured cache");
cache
.set("ckey", "cval".to_string())
.await
.expect("Failed to set");
assert_eq!(
cache.get("ckey").await.expect("Failed to get"),
Some("cval".to_string())
);
assert!(cache.exists("ckey").await.expect("exists failed"));
}
#[tokio::test]
async fn test_mock_cache_basic_operations() {
let cache = MockCache::new();
cache
.set("key1", "value1".to_string())
.await
.expect("Failed to set");
let value = cache.get("key1").await.expect("Failed to get");
assert_eq!(value, Some("value1".to_string()));
assert!(cache.exists("key1").await.expect("exists failed"));
assert!(!cache.exists("nonexistent").await.expect("exists failed"));
assert!(cache.delete("key1").await.expect("delete failed"));
assert!(!cache.exists("key1").await.expect("exists failed"));
assert!(
!cache.delete("key1").await.expect("delete failed"),
"再次删除返回 false"
);
}
#[tokio::test]
async fn test_mock_cache_get_nonexistent() {
let cache = MockCache::new();
let value = cache.get("nonexistent_key").await.expect("Failed to get");
assert_eq!(value, None);
}
#[tokio::test]
async fn test_mock_cache_overwrite() {
let cache = MockCache::new();
cache
.set("key", "value1".to_string())
.await
.expect("Failed to set");
cache
.set("key", "value2".to_string())
.await
.expect("Failed to set");
let value = cache.get("key").await.expect("Failed to get");
assert_eq!(value, Some("value2".to_string()));
}
#[tokio::test]
async fn test_mock_cache_with_delay() {
let cache = MockCache::with_delay(10);
let start = std::time::Instant::now();
cache.set("key", "value".to_string()).await.unwrap();
let elapsed = start.elapsed();
assert!(elapsed.as_millis() >= 10);
let start = std::time::Instant::now();
let _ = cache.get("key").await.unwrap();
let elapsed = start.elapsed();
assert!(elapsed.as_millis() >= 10);
}
#[tokio::test]
async fn test_mock_cache_delete_operation() {
let cache = MockCache::new();
assert!(!cache.delete("nonexistent").await.expect("delete failed"));
cache.set("key", "value".to_string()).await.unwrap();
assert!(cache.delete("key").await.expect("delete failed"));
assert_eq!(cache.get("key").await.expect("get failed"), None);
assert!(!cache.exists("key").await.expect("exists failed"));
}
#[tokio::test]
async fn test_mock_cache_exists_operation() {
let cache = MockCache::new();
assert!(!cache.exists("key1").await.expect("exists failed"));
cache.set("key1", "value1".to_string()).await.unwrap();
assert!(cache.exists("key1").await.expect("exists failed"));
cache.delete("key1").await.unwrap();
assert!(!cache.exists("key1").await.expect("exists failed"));
}
#[tokio::test]
async fn test_mock_cache_thread_safety() {
use std::sync::Arc;
use tokio::task;
let cache = Arc::new(MockCache::new());
let mut handles = vec![];
for i in 0..10 {
let cache_clone = cache.clone();
let handle = task::spawn(async move {
cache_clone
.set(&format!("key{}", i), format!("value{}", i))
.await
.unwrap();
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
for i in 0..10 {
let value = cache.get(&format!("key{}", i)).await.expect("get failed");
assert_eq!(value, Some(format!("value{}", i)));
}
}
#[tokio::test]
async fn test_mock_cache_default_equals_new() {
let default_cache = MockCache::default();
let new_cache = MockCache::new();
assert_eq!(default_cache.get("any").await.expect("get failed"), None);
assert_eq!(new_cache.get("any").await.expect("get failed"), None);
default_cache
.set("k", "v".to_string())
.await
.expect("default mock set should succeed");
assert_eq!(
default_cache.get("k").await.expect("get failed"),
Some("v".to_string())
);
}
#[tokio::test]
async fn test_mock_cache_with_delay_delete_measures_delay() {
let cache = MockCache::with_delay(15);
cache
.set("delay_key", "v".to_string())
.await
.expect("set should succeed");
let start = std::time::Instant::now();
let deleted = cache.delete("delay_key").await.expect("delete failed");
let elapsed = start.elapsed();
assert!(deleted, "existing key should be deleted");
assert!(
elapsed.as_millis() >= 15,
"delete should respect delay_ms, got {}ms",
elapsed.as_millis()
);
}
#[tokio::test]
async fn test_mock_cache_with_delay_exists_measures_delay() {
let cache = MockCache::with_delay(15);
cache
.set("k", "v".to_string())
.await
.expect("set should succeed");
let start = std::time::Instant::now();
let exists = cache.exists("k").await.expect("exists failed");
let elapsed = start.elapsed();
assert!(exists, "key should exist");
assert!(
elapsed.as_millis() >= 15,
"exists should respect delay_ms, got {}ms",
elapsed.as_millis()
);
}
#[tokio::test]
async fn test_oxcache_adapter_ttl_expiration() {
let cache = OxCacheAdapter::builder()
.ttl(std::time::Duration::from_millis(100))
.build()
.await
.expect("Failed to build cache with TTL");
cache
.set("expiring_key", "expiring_value".to_string())
.await
.expect("Failed to set");
assert_eq!(
cache.get("expiring_key").await.expect("get failed"),
Some("expiring_value".to_string())
);
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
assert_eq!(
cache.get("expiring_key").await.expect("get failed"),
None,
"expired key should return None"
);
}
#[tokio::test]
async fn test_oxcache_adapter_exists_after_ttl_expiration() {
let cache = OxCacheAdapter::builder()
.ttl(std::time::Duration::from_millis(80))
.build()
.await
.expect("Failed to build cache with TTL");
cache
.set("ttl_exists_key", "v".to_string())
.await
.expect("Failed to set");
assert!(cache.exists("ttl_exists_key").await.expect("exists failed"));
tokio::time::sleep(std::time::Duration::from_millis(150)).await;
assert!(
!cache.exists("ttl_exists_key").await.expect("exists failed"),
"exists should return false after TTL expiration"
);
}
#[tokio::test]
async fn test_oxcache_adapter_delete_after_ttl_expiration() {
let cache = OxCacheAdapter::builder()
.ttl(std::time::Duration::from_millis(80))
.build()
.await
.expect("Failed to build cache with TTL");
cache
.set("ttl_delete_key", "v".to_string())
.await
.expect("Failed to set");
tokio::time::sleep(std::time::Duration::from_millis(150)).await;
let deleted = cache.delete("ttl_delete_key").await.expect("delete failed");
assert!(
!deleted,
"delete should return false for expired key (exists short-circuit)"
);
}
#[tokio::test]
async fn test_oxcache_adapter_builder_ttl_only() {
let cache = OxCacheAdapter::builder()
.ttl(std::time::Duration::from_secs(60))
.build()
.await
.expect("Failed to build cache with TTL only");
cache
.set("ttl_only_key", "ttl_only_value".to_string())
.await
.expect("Failed to set");
assert_eq!(
cache.get("ttl_only_key").await.expect("get failed"),
Some("ttl_only_value".to_string())
);
assert!(cache.exists("ttl_only_key").await.expect("exists failed"));
}
#[tokio::test]
async fn test_oxcache_adapter_builder_capacity_only() {
let cache = OxCacheAdapter::builder()
.capacity(50)
.build()
.await
.expect("Failed to build cache with capacity only");
cache
.set("cap_only_key", "cap_only_value".to_string())
.await
.expect("Failed to set");
assert_eq!(
cache.get("cap_only_key").await.expect("get failed"),
Some("cap_only_value".to_string())
);
assert!(cache.exists("cap_only_key").await.expect("exists failed"));
}
#[tokio::test]
async fn test_oxcache_adapter_empty_key() {
let cache = OxCacheAdapter::new().expect("Failed to create cache");
cache
.set("", "empty_key_value".to_string())
.await
.expect("Failed to set with empty key");
assert_eq!(
cache.get("").await.expect("get failed"),
Some("empty_key_value".to_string())
);
assert!(cache.exists("").await.expect("exists failed"));
assert!(cache.delete("").await.expect("delete failed"));
assert!(!cache.exists("").await.expect("exists failed"));
}
#[tokio::test]
async fn test_oxcache_adapter_special_characters_in_key() {
let cache = OxCacheAdapter::new().expect("Failed to create cache");
let keys = [
"user:1",
"namespace::key",
"key with spaces",
"key\twith\ttabs",
"key\nwith\nnewlines",
"中文键",
"key\"with\"quotes",
"key'with'apostrophes",
];
for key in &keys {
let value = format!("value_for_{}", key);
cache
.set(key, value.clone())
.await
.expect("Failed to set with special char key");
assert_eq!(
cache.get(key).await.expect("get failed"),
Some(value),
"get should return the set value for key: {:?}",
key
);
}
}
#[tokio::test]
async fn test_oxcache_adapter_large_value() {
let cache = OxCacheAdapter::new().expect("Failed to create cache");
let large_value = "x".repeat(100_000);
cache
.set("large_key", large_value.clone())
.await
.expect("Failed to set large value");
let retrieved = cache.get("large_key").await.expect("get failed");
assert_eq!(retrieved.as_ref().map(|v| v.len()), Some(100_000));
assert_eq!(retrieved, Some(large_value));
}
#[tokio::test]
async fn test_oxcache_adapter_concurrent_access() {
use std::sync::Arc;
use tokio::task;
let cache = Arc::new(OxCacheAdapter::new().expect("Failed to create cache"));
let mut handles = vec![];
for i in 0..10 {
let cache_clone = cache.clone();
let handle = task::spawn(async move {
cache_clone
.set(&format!("ckey{}", i), format!("cvalue{}", i))
.await
.expect("Failed to set");
});
handles.push(handle);
}
for handle in handles {
handle.await.expect("Task panicked");
}
for i in 0..10 {
assert_eq!(
cache.get(&format!("ckey{}", i)).await.expect("get failed"),
Some(format!("cvalue{}", i)),
"concurrent write for key {} should be visible",
i
);
}
}
#[tokio::test]
async fn test_oxcache_adapter_delete_nonexistent_after_set() {
let cache = OxCacheAdapter::new().expect("Failed to create cache");
assert!(
!cache.delete("never_set_key").await.expect("delete failed"),
"delete on never-set key should return false"
);
cache
.set("temp_key", "temp_value".to_string())
.await
.expect("Failed to set");
assert!(cache.delete("temp_key").await.expect("delete failed"));
assert!(
!cache.delete("temp_key").await.expect("delete failed"),
"delete on already-deleted key should return false"
);
}
#[tokio::test]
async fn test_oxcache_adapter_overwrite_does_not_reset_ttl() {
let cache = OxCacheAdapter::builder()
.ttl(std::time::Duration::from_secs(60))
.build()
.await
.expect("Failed to build cache");
cache
.set("ow_key", "v1".to_string())
.await
.expect("Failed to set v1");
cache
.set("ow_key", "v2".to_string())
.await
.expect("Failed to set v2");
assert_eq!(
cache.get("ow_key").await.expect("get failed"),
Some("v2".to_string()),
"overwrite should replace value"
);
}
}