use bytes::Bytes;
use serde::{de::DeserializeOwned, Serialize};
use std::cell::RefCell;
mod cache;
use cache::LRUCache;
thread_local! {
static CACHE: RefCell<Option<LRUCache>> = const { RefCell::new(None) };
}
#[derive(Clone)]
pub struct LocalCache {
capacity: usize,
ttl: u64,
}
impl LocalCache {
pub fn initialize(capacity: usize, ttl: u64) -> Self {
LocalCache { capacity, ttl }
}
#[deprecated(since = "0.4.0", note = "Use initialize() instead")]
pub fn new(capacity: usize, ttl: u64) -> Self {
LocalCache { capacity, ttl }
}
fn initialize_cache_if_none(&self) {
CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
if cache.is_none() {
*cache = Some(LRUCache::new(self.capacity, self.ttl));
}
});
}
pub fn get_item(&self, key: &str) -> Option<Bytes> {
self.initialize_cache_if_none();
CACHE.with(|cache| {
if let Some(cache) = cache.borrow_mut().as_mut() {
cache.get_item(key)
} else {
None
}
})
}
pub fn add_item(&self, key: &str, value: Bytes) {
self.initialize_cache_if_none();
CACHE.with(|cache| {
if let Some(cache) = cache.borrow_mut().as_mut() {
cache.add_item(key.to_string(), value)
}
})
}
pub fn add_struct<T: Serialize>(&self, key: &str, value: T) {
let bytes = bincode::serialize(&value).unwrap(); self.add_item(key, Bytes::from(bytes));
}
pub fn get_struct<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
let bytes = self.get_item(key)?;
bincode::deserialize(&bytes).ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
use serde::Serialize;
use std::thread;
use std::thread::sleep;
use std::time::Duration;
#[test]
fn test_capacity_based_eviction() {
let cache = LocalCache::initialize(3, 60);
cache.add_item("key1", Bytes::from("value1"));
cache.add_item("key2", Bytes::from("value2"));
cache.add_item("key3", Bytes::from("value3"));
assert_eq!(cache.get_item("key1"), Some(Bytes::from("value1")));
assert_eq!(cache.get_item("key2"), Some(Bytes::from("value2")));
assert_eq!(cache.get_item("key3"), Some(Bytes::from("value3")));
cache.add_item("key4", Bytes::from("value4"));
assert_eq!(cache.get_item("key1"), None);
assert_eq!(cache.get_item("key2"), Some(Bytes::from("value2")));
assert_eq!(cache.get_item("key3"), Some(Bytes::from("value3")));
assert_eq!(cache.get_item("key4"), Some(Bytes::from("value4")));
}
#[test]
fn test_get_item_updates_order() {
let cache = LocalCache::initialize(3, 60);
cache.add_item("key1", Bytes::from("value1"));
cache.add_item("key2", Bytes::from("value2"));
cache.add_item("key3", Bytes::from("value3"));
cache.get_item("key1");
cache.add_item("key4", Bytes::from("value4"));
assert_eq!(cache.get_item("key1"), Some(Bytes::from("value1")));
assert_eq!(cache.get_item("key2"), None);
assert_eq!(cache.get_item("key3"), Some(Bytes::from("value3")));
assert_eq!(cache.get_item("key4"), Some(Bytes::from("value4")));
}
#[test]
fn test_ttl_expiration() {
let cache = LocalCache::initialize(3, 2);
cache.add_item("key1", Bytes::from("value1"));
assert_eq!(cache.get_item("key1"), Some(Bytes::from("value1")));
sleep(Duration::from_secs(3));
assert_eq!(cache.get_item("key1"), None);
}
#[test]
fn test_no_ttl_expiration() {
let cache = LocalCache::initialize(3, 0);
cache.add_item("key1", Bytes::from("value1"));
assert_eq!(cache.get_item("key1"), Some(Bytes::from("value1")));
sleep(Duration::from_secs(3));
assert_eq!(cache.get_item("key1"), Some(Bytes::from("value1")));
}
#[test]
fn test_add_and_get_struct() {
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
struct TestStruct {
field1: String,
field2: i32,
}
let cache = LocalCache::initialize(3, 60);
let test_struct = TestStruct {
field1: "Hello".to_string(),
field2: 42,
};
cache.add_struct("test_key", test_struct.clone());
let retrieved_struct: Option<TestStruct> = cache.get_struct("test_key");
assert_eq!(retrieved_struct, Some(test_struct.clone()));
let non_existent: Option<TestStruct> = cache.get_struct("non_existent_key");
assert_eq!(non_existent, None);
}
#[test]
fn test_thread_local_isolation() {
let cache = LocalCache::initialize(3, 60);
let cache_clone = cache.clone();
cache.add_item("main_key", Bytes::from("main_value"));
let thread_handle = thread::spawn(move || {
cache_clone.get_item("main_key"); cache_clone.add_item("thread_key", Bytes::from("thread_value"));
});
thread_handle.join().unwrap();
assert_eq!(cache.get_item("main_key"), Some(Bytes::from("main_value")));
assert_eq!(cache.get_item("thread_key"), None);
}
}