1use moka::future::Cache as MokaCache;
2use std::hash::Hash;
3use std::time::Duration;
4use tracing::{debug, info};
5
6use crate::pmc::models::PmcFullText;
7
8#[derive(Debug, Clone)]
10pub struct CacheConfig {
11 pub max_capacity: u64,
13 pub time_to_live: Duration,
15}
16
17impl Default for CacheConfig {
18 fn default() -> Self {
19 Self {
20 max_capacity: 1000,
21 time_to_live: Duration::from_secs(7 * 24 * 60 * 60), }
23 }
24}
25
26#[derive(Clone)]
28pub struct MemoryCache<K, V> {
29 cache: MokaCache<K, V>,
30}
31
32impl<K, V> MemoryCache<K, V>
33where
34 K: Hash + Eq + Clone + Send + Sync + 'static,
35 V: Clone + Send + Sync + 'static,
36{
37 pub fn new(config: &CacheConfig) -> Self {
38 let cache = MokaCache::builder()
39 .max_capacity(config.max_capacity)
40 .time_to_live(config.time_to_live)
41 .build();
42
43 Self { cache }
44 }
45
46 pub async fn get(&self, key: &K) -> Option<V> {
47 let result = self.cache.get(key).await;
48 if result.is_some() {
49 debug!("Cache hit");
50 } else {
51 debug!("Cache miss");
52 }
53 result
54 }
55
56 pub async fn insert(&self, key: K, value: V) {
57 self.cache.insert(key, value).await;
58 info!("Item cached");
59 }
60
61 pub async fn clear(&self) {
62 self.cache.invalidate_all();
63 info!("Cache cleared");
64 }
65
66 pub fn entry_count(&self) -> u64 {
67 self.cache.entry_count()
68 }
69
70 pub async fn sync(&self) {
71 self.cache.run_pending_tasks().await;
72 }
73}
74
75pub type PmcCache = MemoryCache<String, PmcFullText>;
77
78pub fn create_cache(config: &CacheConfig) -> PmcCache {
80 MemoryCache::new(config)
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[tokio::test]
88 async fn test_memory_cache_basic() {
89 let config = CacheConfig {
90 max_capacity: 10,
91 time_to_live: Duration::from_secs(60),
92 };
93 let cache = MemoryCache::<String, String>::new(&config);
94
95 cache.insert("key1".to_string(), "value1".to_string()).await;
97 assert_eq!(
98 cache.get(&"key1".to_string()).await,
99 Some("value1".to_string())
100 );
101
102 assert_eq!(cache.get(&"nonexistent".to_string()).await, None);
104
105 cache.clear().await;
107 assert_eq!(cache.get(&"key1".to_string()).await, None);
108 }
109
110 #[tokio::test]
111 async fn test_cache_entry_count() {
112 let config = CacheConfig::default();
113 let cache = MemoryCache::<String, String>::new(&config);
114
115 assert_eq!(cache.entry_count(), 0);
116
117 cache.insert("key1".to_string(), "value1".to_string()).await;
118 cache.sync().await;
119 assert_eq!(cache.entry_count(), 1);
120
121 cache.insert("key2".to_string(), "value2".to_string()).await;
122 cache.sync().await;
123 assert_eq!(cache.entry_count(), 2);
124
125 cache.clear().await;
126 cache.sync().await;
127 assert_eq!(cache.entry_count(), 0);
128 }
129}