1use std::collections::HashMap;
15use crate::session_store::{SessionStore, MemoryEntry};
16
17pub const DEFAULT_SCOPE: &str = "global";
21
22pub fn flow_scope(flow_name: &str) -> String {
24 format!("flow:{}", flow_name)
25}
26
27pub fn daemon_scope(daemon_name: &str) -> String {
29 format!("daemon:{}", daemon_name)
30}
31
32#[derive(Debug)]
36pub struct ScopedSessionManager {
37 base_path: String,
39 scopes: HashMap<String, SessionStore>,
41}
42
43impl ScopedSessionManager {
44 pub fn new(base_path: &str) -> Self {
47 let mut mgr = ScopedSessionManager {
48 base_path: base_path.to_string(),
49 scopes: HashMap::new(),
50 };
51 mgr.ensure_scope(DEFAULT_SCOPE);
53 mgr
54 }
55
56 fn ensure_scope(&mut self, scope: &str) -> &mut SessionStore {
58 if !self.scopes.contains_key(scope) {
59 let source = format!("{}__{}", self.base_path, scope.replace(':', "_"));
60 let store = SessionStore::new(&source);
61 self.scopes.insert(scope.to_string(), store);
62 }
63 self.scopes.get_mut(scope).unwrap()
64 }
65
66 fn get_scope(&self, scope: &str) -> Option<&SessionStore> {
68 self.scopes.get(scope)
69 }
70
71 pub fn remember(&mut self, scope: &str, key: &str, value: &str, source_step: &str) {
75 self.ensure_scope(scope).remember(key, value, source_step);
76 }
77
78 pub fn recall(&mut self, scope: &str, key: &str) -> Option<MemoryEntry> {
80 self.ensure_scope(scope).recall(key).cloned()
81 }
82
83 pub fn persist(&mut self, scope: &str, key: &str, value: &str, source_step: &str) {
85 self.ensure_scope(scope).persist(key, value, source_step);
86 }
87
88 pub fn retrieve(&mut self, scope: &str, key: &str) -> Option<MemoryEntry> {
90 self.ensure_scope(scope).retrieve(key).cloned()
91 }
92
93 pub fn query(&mut self, scope: &str, query: &str) -> Vec<MemoryEntry> {
95 self.ensure_scope(scope)
96 .retrieve_query(query)
97 .into_iter()
98 .cloned()
99 .collect()
100 }
101
102 pub fn mutate(&mut self, scope: &str, key: &str, new_value: &str, source_step: &str) -> bool {
104 self.ensure_scope(scope).mutate(key, new_value, source_step)
105 }
106
107 pub fn purge(&mut self, scope: &str, key: &str) -> bool {
109 self.ensure_scope(scope).purge(key)
110 }
111
112 pub fn flush(&mut self, scope: &str) -> Result<(), String> {
114 self.ensure_scope(scope).flush()
115 }
116
117 pub fn flush_all(&mut self) -> Vec<(String, Result<(), String>)> {
119 let scope_names: Vec<String> = self.scopes.keys().cloned().collect();
120 let mut results = Vec::new();
121 for name in scope_names {
122 let result = self.scopes.get(&name).map(|s| s.flush()).unwrap_or(Ok(()));
123 results.push((name, result));
124 }
125 results
126 }
127
128 pub fn list_scopes(&self) -> Vec<&str> {
132 let mut names: Vec<&str> = self.scopes.keys().map(|s| s.as_str()).collect();
133 names.sort();
134 names
135 }
136
137 pub fn scope_count(&self) -> usize {
139 self.scopes.len()
140 }
141
142 pub fn memory_count(&self, scope: &str) -> usize {
144 self.get_scope(scope).map(|s| s.memory_count()).unwrap_or(0)
145 }
146
147 pub fn store_count(&self, scope: &str) -> usize {
149 self.get_scope(scope).map(|s| s.store_count()).unwrap_or(0)
150 }
151
152 pub fn total_memory_count(&self) -> usize {
154 self.scopes.values().map(|s| s.memory_count()).sum()
155 }
156
157 pub fn total_store_count(&self) -> usize {
159 self.scopes.values().map(|s| s.store_count()).sum()
160 }
161
162 pub fn list_entries(&mut self, scope: &str) -> Vec<ScopedEntry> {
164 let store = self.ensure_scope(scope);
165 let mut entries = Vec::new();
166
167 for entry in store.memory_entries() {
168 entries.push(ScopedEntry {
169 scope: scope.to_string(),
170 layer: "memory".to_string(),
171 key: entry.key.clone(),
172 value: entry.value.clone(),
173 timestamp: entry.timestamp,
174 source_step: entry.source_step.clone(),
175 });
176 }
177
178 entries
183 }
184
185 pub fn summary(&self) -> Vec<ScopeSummary> {
187 let mut result = Vec::new();
188 for (name, store) in &self.scopes {
189 result.push(ScopeSummary {
190 scope: name.clone(),
191 memory_count: store.memory_count(),
192 store_count: store.store_count(),
193 });
194 }
195 result.sort_by(|a, b| a.scope.cmp(&b.scope));
196 result
197 }
198}
199
200#[derive(Debug, Clone, serde::Serialize)]
202pub struct ScopedEntry {
203 pub scope: String,
204 pub layer: String,
205 pub key: String,
206 pub value: String,
207 pub timestamp: u64,
208 pub source_step: String,
209}
210
211#[derive(Debug, Clone, serde::Serialize)]
213pub struct ScopeSummary {
214 pub scope: String,
215 pub memory_count: usize,
216 pub store_count: usize,
217}
218
219#[cfg(test)]
222mod tests {
223 use super::*;
224
225 fn test_manager(name: &str) -> ScopedSessionManager {
226 let base = std::env::temp_dir().join(format!("axon_scope_test_{name}"));
227 ScopedSessionManager::new(base.to_str().unwrap())
228 }
229
230 #[test]
231 fn default_scope_created() {
232 let mgr = test_manager("default");
233 assert!(mgr.list_scopes().contains(&"global"));
234 assert_eq!(mgr.scope_count(), 1);
235 }
236
237 #[test]
238 fn remember_recall_in_scope() {
239 let mut mgr = test_manager("rem_recall");
240 mgr.remember("flow:analyze", "result", "42", "step1");
241 let entry = mgr.recall("flow:analyze", "result").unwrap();
242 assert_eq!(entry.value, "42");
243
244 assert!(mgr.recall("global", "result").is_none());
246 assert!(mgr.recall("flow:other", "result").is_none());
247 }
248
249 #[test]
250 fn persist_retrieve_in_scope() {
251 let mut mgr = test_manager("persist_ret");
252 mgr.persist("daemon:worker", "config", "max_retries=3", "init");
253 let entry = mgr.retrieve("daemon:worker", "config").unwrap();
254 assert_eq!(entry.value, "max_retries=3");
255
256 assert!(mgr.retrieve("global", "config").is_none());
258 }
259
260 #[test]
261 fn mutate_in_scope() {
262 let mut mgr = test_manager("mutate");
263 mgr.persist("flow:a", "key", "old", "s1");
264 assert!(mgr.mutate("flow:a", "key", "new", "s2"));
265 assert_eq!(mgr.retrieve("flow:a", "key").unwrap().value, "new");
266
267 assert!(!mgr.mutate("flow:b", "key", "val", "s3"));
269 }
270
271 #[test]
272 fn purge_in_scope() {
273 let mut mgr = test_manager("purge");
274 mgr.persist("flow:x", "temp", "data", "s1");
275 assert!(mgr.purge("flow:x", "temp"));
276 assert!(mgr.retrieve("flow:x", "temp").is_none());
277
278 mgr.persist("flow:y", "keep", "data", "s1");
280 assert!(!mgr.purge("flow:z", "keep"));
281 assert!(mgr.retrieve("flow:y", "keep").is_some());
282 }
283
284 #[test]
285 fn query_in_scope() {
286 let mut mgr = test_manager("query");
287 mgr.persist("flow:a", "analysis_1", "result1", "s1");
288 mgr.persist("flow:a", "analysis_2", "result2", "s2");
289 mgr.persist("flow:b", "analysis_3", "result3", "s3");
290
291 let results = mgr.query("flow:a", "analysis");
292 assert_eq!(results.len(), 2);
293
294 let results_b = mgr.query("flow:b", "analysis");
296 assert_eq!(results_b.len(), 1);
297 }
298
299 #[test]
300 fn scope_counts() {
301 let mut mgr = test_manager("counts");
302 mgr.remember("global", "a", "1", "s");
303 mgr.remember("global", "b", "2", "s");
304 mgr.persist("flow:x", "c", "3", "s");
305
306 assert_eq!(mgr.memory_count("global"), 2);
307 assert_eq!(mgr.store_count("flow:x"), 1);
308 assert_eq!(mgr.total_memory_count(), 2);
309 assert_eq!(mgr.total_store_count(), 1);
310 }
311
312 #[test]
313 fn list_scopes_sorted() {
314 let mut mgr = test_manager("list");
315 mgr.remember("flow:z", "k", "v", "s");
316 mgr.remember("daemon:a", "k", "v", "s");
317 let scopes = mgr.list_scopes();
318 assert!(scopes.len() >= 3);
319 for i in 1..scopes.len() {
321 assert!(scopes[i - 1] <= scopes[i]);
322 }
323 }
324
325 #[test]
326 fn summary_includes_all_scopes() {
327 let mut mgr = test_manager("summary");
328 mgr.remember("global", "a", "1", "s");
329 mgr.persist("flow:report", "b", "2", "s");
330
331 let summary = mgr.summary();
332 assert!(summary.len() >= 2);
333
334 let global = summary.iter().find(|s| s.scope == "global").unwrap();
335 assert_eq!(global.memory_count, 1);
336
337 let flow = summary.iter().find(|s| s.scope == "flow:report").unwrap();
338 assert_eq!(flow.store_count, 1);
339 }
340
341 #[test]
342 fn flow_scope_and_daemon_scope_helpers() {
343 assert_eq!(flow_scope("analyze"), "flow:analyze");
344 assert_eq!(daemon_scope("worker"), "daemon:worker");
345 }
346
347 #[test]
348 fn isolated_scopes_no_leakage() {
349 let mut mgr = test_manager("isolation");
350
351 mgr.remember("global", "status", "global_val", "s");
353 mgr.remember("flow:a", "status", "flow_a_val", "s");
354 mgr.remember("daemon:d", "status", "daemon_val", "s");
355
356 assert_eq!(mgr.recall("global", "status").unwrap().value, "global_val");
357 assert_eq!(mgr.recall("flow:a", "status").unwrap().value, "flow_a_val");
358 assert_eq!(mgr.recall("daemon:d", "status").unwrap().value, "daemon_val");
359 }
360
361 #[test]
362 fn nonexistent_scope_returns_zero_counts() {
363 let mgr = test_manager("nonexist");
364 assert_eq!(mgr.memory_count("flow:phantom"), 0);
365 assert_eq!(mgr.store_count("daemon:ghost"), 0);
366 }
367
368 #[test]
369 fn scoped_entry_serializes() {
370 let entry = ScopedEntry {
371 scope: "flow:test".into(),
372 layer: "memory".into(),
373 key: "k".into(),
374 value: "v".into(),
375 timestamp: 12345,
376 source_step: "s1".into(),
377 };
378 let json = serde_json::to_string(&entry).unwrap();
379 assert!(json.contains("\"scope\":\"flow:test\""));
380 assert!(json.contains("\"layer\":\"memory\""));
381 }
382
383 #[test]
384 fn scope_summary_serializes() {
385 let summary = ScopeSummary {
386 scope: "global".into(),
387 memory_count: 3,
388 store_count: 1,
389 };
390 let json = serde_json::to_string(&summary).unwrap();
391 assert!(json.contains("\"scope\":\"global\""));
392 assert!(json.contains("\"memory_count\":3"));
393 }
394}