Skip to main content

robinpath_modules/modules/
cache_mod.rs

1use robinpath::{RobinPath, Value};
2use std::collections::HashMap;
3use std::sync::{Arc, Mutex};
4use std::time::{Duration, Instant};
5
6struct CacheEntry {
7    value: Value,
8    expires_at: Option<Instant>,
9}
10
11struct CacheState {
12    entries: HashMap<String, CacheEntry>,
13}
14
15impl CacheState {
16    fn new() -> Self {
17        Self {
18            entries: HashMap::new(),
19        }
20    }
21
22    fn cleanup(&mut self) {
23        let now = Instant::now();
24        self.entries.retain(|_, entry| {
25            entry.expires_at.map_or(true, |exp| exp > now)
26        });
27    }
28}
29
30pub fn register(rp: &mut RobinPath) {
31    let state = Arc::new(Mutex::new(CacheState::new()));
32
33    let s = state.clone();
34    rp.register_builtin("cache.set", move |args, _| {
35        let key = args.first().map(|v| v.to_display_string()).unwrap_or_default();
36        let value = args.get(1).cloned().unwrap_or(Value::Null);
37        let ttl_secs = args.get(2).map(|v| v.to_number());
38        let expires_at = ttl_secs.map(|secs| Instant::now() + Duration::from_secs_f64(secs));
39        let mut cache = s.lock().unwrap();
40        cache.entries.insert(key, CacheEntry { value, expires_at });
41        Ok(Value::Bool(true))
42    });
43
44    let s = state.clone();
45    rp.register_builtin("cache.get", move |args, _| {
46        let key = args.first().map(|v| v.to_display_string()).unwrap_or_default();
47        let default = args.get(1).cloned().unwrap_or(Value::Null);
48        let mut cache = s.lock().unwrap();
49        let now = Instant::now();
50        if let Some(entry) = cache.entries.get(&key) {
51            if entry.expires_at.map_or(true, |exp| exp > now) {
52                return Ok(entry.value.clone());
53            }
54            cache.entries.remove(&key);
55        }
56        Ok(default)
57    });
58
59    let s = state.clone();
60    rp.register_builtin("cache.has", move |args, _| {
61        let key = args.first().map(|v| v.to_display_string()).unwrap_or_default();
62        let mut cache = s.lock().unwrap();
63        let now = Instant::now();
64        if let Some(entry) = cache.entries.get(&key) {
65            if entry.expires_at.map_or(true, |exp| exp > now) {
66                return Ok(Value::Bool(true));
67            }
68            cache.entries.remove(&key);
69        }
70        Ok(Value::Bool(false))
71    });
72
73    let s = state.clone();
74    rp.register_builtin("cache.delete", move |args, _| {
75        let key = args.first().map(|v| v.to_display_string()).unwrap_or_default();
76        let mut cache = s.lock().unwrap();
77        let removed = cache.entries.remove(&key).is_some();
78        Ok(Value::Bool(removed))
79    });
80
81    let s = state.clone();
82    rp.register_builtin("cache.clear", move |_args, _| {
83        let mut cache = s.lock().unwrap();
84        cache.entries.clear();
85        Ok(Value::Bool(true))
86    });
87
88    let s = state.clone();
89    rp.register_builtin("cache.keys", move |_args, _| {
90        let mut cache = s.lock().unwrap();
91        cache.cleanup();
92        let keys: Vec<Value> = cache
93            .entries
94            .keys()
95            .map(|k| Value::String(k.clone()))
96            .collect();
97        Ok(Value::Array(keys))
98    });
99
100    let s = state.clone();
101    rp.register_builtin("cache.values", move |_args, _| {
102        let mut cache = s.lock().unwrap();
103        cache.cleanup();
104        let values: Vec<Value> = cache
105            .entries
106            .values()
107            .map(|entry| entry.value.clone())
108            .collect();
109        Ok(Value::Array(values))
110    });
111
112    let s = state.clone();
113    rp.register_builtin("cache.size", move |_args, _| {
114        let mut cache = s.lock().unwrap();
115        cache.cleanup();
116        Ok(Value::Number(cache.entries.len() as f64))
117    });
118
119    let s = state.clone();
120    rp.register_builtin("cache.ttl", move |args, _| {
121        let key = args.first().map(|v| v.to_display_string()).unwrap_or_default();
122        let cache = s.lock().unwrap();
123        if let Some(entry) = cache.entries.get(&key) {
124            match entry.expires_at {
125                Some(exp) => {
126                    let now = Instant::now();
127                    if exp > now {
128                        Ok(Value::Number((exp - now).as_secs_f64()))
129                    } else {
130                        Ok(Value::Number(-1.0))
131                    }
132                }
133                None => Ok(Value::Number(-1.0)),
134            }
135        } else {
136            Ok(Value::Null)
137        }
138    });
139
140    let s = state.clone();
141    rp.register_builtin("cache.setMany", move |args, _| {
142        let obj = args.first().cloned().unwrap_or(Value::Null);
143        let ttl_secs = args.get(1).map(|v| v.to_number());
144        let expires_at = ttl_secs.map(|secs| Instant::now() + Duration::from_secs_f64(secs));
145        if let Value::Object(map) = &obj {
146            let mut cache = s.lock().unwrap();
147            for (key, value) in map {
148                cache.entries.insert(
149                    key.clone(),
150                    CacheEntry {
151                        value: value.clone(),
152                        expires_at,
153                    },
154                );
155            }
156        }
157        Ok(Value::Bool(true))
158    });
159
160    let s = state.clone();
161    rp.register_builtin("cache.getMany", move |args, _| {
162        let keys = args.first().cloned().unwrap_or(Value::Null);
163        if let Value::Array(arr) = &keys {
164            let mut cache = s.lock().unwrap();
165            let now = Instant::now();
166            let mut result = indexmap::IndexMap::new();
167            for key_val in arr {
168                let key = key_val.to_display_string();
169                if let Some(entry) = cache.entries.get(&key) {
170                    if entry.expires_at.map_or(true, |exp| exp > now) {
171                        result.insert(key, entry.value.clone());
172                    } else {
173                        cache.entries.remove(&key);
174                        result.insert(key, Value::Null);
175                    }
176                } else {
177                    result.insert(key, Value::Null);
178                }
179            }
180            Ok(Value::Object(result))
181        } else {
182            Ok(Value::Object(indexmap::IndexMap::new()))
183        }
184    });
185
186    let s = state.clone();
187    rp.register_builtin("cache.deleteMany", move |args, _| {
188        let keys = args.first().cloned().unwrap_or(Value::Null);
189        if let Value::Array(arr) = &keys {
190            let mut cache = s.lock().unwrap();
191            let mut count = 0;
192            for key_val in arr {
193                let key = key_val.to_display_string();
194                if cache.entries.remove(&key).is_some() {
195                    count += 1;
196                }
197            }
198            Ok(Value::Number(count as f64))
199        } else {
200            Ok(Value::Number(0.0))
201        }
202    });
203}