use cached::{proc_macro::cached, Cached, SizedCache};
use std::hash::Hash;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy)]
pub struct CacheConfig {
pub default_size: usize,
pub default_ttl: u64,
pub enable_caching: bool,
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
default_size: 1024,
default_ttl: 3600, enable_caching: true,
}
}
}
pub struct TTLSizedCache<K, V> {
cache: SizedCache<K, (V, Instant)>,
ttl: Duration,
}
impl<K, V> TTLSizedCache<K, V>
where
K: Hash + Eq + Clone,
V: Clone,
{
pub fn new(size: usize, ttl_seconds: u64) -> Self {
Self {
cache: SizedCache::with_size(size),
ttl: Duration::from_secs(ttl_seconds),
}
}
pub fn insert(&mut self, key: K, value: V) {
let now = Instant::now();
self.cache.cache_set(key, (value, now));
}
pub fn get(&mut self, key: &K) -> Option<V> {
if let Some((value, timestamp)) = self.cache.cache_get(key) {
if timestamp.elapsed() < self.ttl {
return Some(value.clone());
}
self.cache.cache_remove(key);
}
None
}
pub fn remove(&mut self, key: &K) {
self.cache.cache_remove(key);
}
pub fn clear(&mut self) {
self.cache.cache_clear();
}
pub fn len(&self) -> usize {
(self.cache.cache_misses().unwrap_or(0) + self.cache.cache_hits().unwrap_or(0)) as usize
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub struct CacheBuilder {
size: Option<usize>,
ttl: Option<u64>,
thread_safe: bool,
}
impl Default for CacheBuilder {
fn default() -> Self {
Self::new()
}
}
impl CacheBuilder {
pub fn new() -> Self {
Self {
size: None,
ttl: None,
thread_safe: false,
}
}
pub fn with_size(mut self, size: usize) -> Self {
self.size = Some(size);
self
}
pub fn with_ttl(mut self, ttl: u64) -> Self {
self.ttl = Some(ttl);
self
}
pub fn thread_safe(mut self) -> Self {
self.thread_safe = true;
self
}
pub fn build_sized_cache<K, V>(self) -> TTLSizedCache<K, V>
where
K: Hash + Eq + Clone,
V: Clone,
{
let config = CacheConfig::default();
let size = self.size.unwrap_or(config.default_size);
let ttl = self.ttl.unwrap_or(config.default_ttl);
TTLSizedCache::new(size, ttl)
}
}
pub fn memoize_example() {
let mut cache = TTLSizedCache::<String, String>::new(100, 60);
cache.insert("key".to_string(), "value".to_string());
let value = cache.get(&"key".to_string());
println!("Value from cache: {:?}", value);
}
#[cached]
pub fn fibonacci(n: u64) -> u64 {
match n {
0 => 0,
1 => 1,
n => fibonacci(n - 1) + fibonacci(n - 2),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_ttl_sized_cache() {
let mut cache = TTLSizedCache::<i32, &str>::new(5, 1);
cache.insert(1, "one");
cache.insert(2, "two");
assert_eq!(cache.get(&1), Some("one"));
assert_eq!(cache.get(&2), Some("two"));
assert_eq!(cache.get(&3), None);
thread::sleep(Duration::from_secs(2));
assert_eq!(cache.get(&1), None);
assert_eq!(cache.get(&2), None);
for i in 0..10 {
cache.insert(i, "value");
}
for i in 0..5 {
assert_eq!(cache.get(&i), None);
}
for i in 5..10 {
assert_eq!(cache.get(&i), Some("value"));
}
}
#[test]
fn test_cache_builder() {
let cache = CacheBuilder::new()
.with_size(10)
.with_ttl(60)
.build_sized_cache::<String, i32>();
assert!(cache.is_empty());
}
#[test]
fn test_fibonacci() {
let fib10 = fibonacci(10);
let fib20 = fibonacci(20);
assert_eq!(fib10, 55);
assert_eq!(fib20, 6765);
let start = Instant::now();
let fib20_again = fibonacci(20);
let duration = start.elapsed();
assert_eq!(fib20_again, 6765);
assert!(duration.as_millis() < 10);
}
}