github_mcp/core/
cache_manager.rs1use std::collections::HashMap;
4use std::sync::Mutex;
5use std::time::{Duration, Instant};
6
7struct CacheEntry<T> {
8 value: T,
9 expires_at: Instant,
10}
11
12pub struct QueryCache<T> {
14 max_size: usize,
15 ttl: Duration,
16 store: Mutex<HashMap<String, CacheEntry<T>>>,
17}
18
19impl<T: Clone> QueryCache<T> {
20 pub fn new(max_size: usize, ttl: Duration) -> Self {
21 Self {
22 max_size,
23 ttl,
24 store: Mutex::new(HashMap::new()),
25 }
26 }
27
28 pub fn get(&self, key: &str) -> Option<T> {
29 let mut store = self.store.lock().unwrap();
30 let entry = store.get(key)?;
31 if Instant::now() > entry.expires_at {
32 store.remove(key);
33 return None;
34 }
35 Some(entry.value.clone())
36 }
37
38 pub fn set(&self, key: String, value: T) {
39 let mut store = self.store.lock().unwrap();
40 if store.len() >= self.max_size
41 && !store.contains_key(&key)
42 && let Some(oldest_key) = store.keys().next().cloned()
43 {
44 store.remove(&oldest_key);
45 }
46 store.insert(
47 key,
48 CacheEntry {
49 value,
50 expires_at: Instant::now() + self.ttl,
51 },
52 );
53 }
54
55 pub fn clear(&self) {
56 self.store.lock().unwrap().clear();
57 }
58}
59
60impl<T: Clone> Default for QueryCache<T> {
61 fn default() -> Self {
62 Self::new(500, Duration::from_secs(60))
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn returns_a_previously_set_value() {
72 let cache: QueryCache<String> = QueryCache::new(10, Duration::from_secs(60));
73 cache.set("key".to_string(), "value".to_string());
74 assert_eq!(cache.get("key"), Some("value".to_string()));
75 }
76
77 #[test]
78 fn expires_entries_past_their_ttl() {
79 let cache: QueryCache<String> = QueryCache::new(10, Duration::from_millis(1));
80 cache.set("key".to_string(), "value".to_string());
81 std::thread::sleep(Duration::from_millis(10));
82 assert_eq!(cache.get("key"), None);
83 }
84
85 #[test]
86 fn evicts_when_full() {
87 let cache: QueryCache<String> = QueryCache::new(1, Duration::from_secs(60));
88 cache.set("a".to_string(), "1".to_string());
89 cache.set("b".to_string(), "2".to_string());
90 let remaining = [cache.get("a"), cache.get("b")]
95 .into_iter()
96 .flatten()
97 .count();
98 assert_eq!(remaining, 1);
99 }
100}