robinpath_modules/modules/
cache_mod.rs1use 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}