neuron_state_memory/
lib.rs1#![deny(missing_docs)]
2use async_trait::async_trait;
10use layer0::effect::Scope;
11use layer0::error::StateError;
12use layer0::state::{SearchResult, StateStore};
13use std::collections::HashMap;
14use tokio::sync::RwLock;
15
16pub struct MemoryStore {
21 data: RwLock<HashMap<String, serde_json::Value>>,
22}
23
24impl MemoryStore {
25 pub fn new() -> Self {
27 Self {
28 data: RwLock::new(HashMap::new()),
29 }
30 }
31}
32
33impl Default for MemoryStore {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39fn composite_key(scope: &Scope, key: &str) -> String {
41 let scope_str = serde_json::to_string(scope).unwrap_or_else(|_| "unknown".to_string());
42 format!("{scope_str}\0{key}")
43}
44
45fn extract_key<'a>(composite: &'a str, scope_prefix: &str) -> Option<&'a str> {
47 composite
48 .strip_prefix(scope_prefix)
49 .and_then(|rest| rest.strip_prefix('\0'))
50}
51
52#[async_trait]
53impl StateStore for MemoryStore {
54 async fn read(
55 &self,
56 scope: &Scope,
57 key: &str,
58 ) -> Result<Option<serde_json::Value>, StateError> {
59 let ck = composite_key(scope, key);
60 let data = self.data.read().await;
61 Ok(data.get(&ck).cloned())
62 }
63
64 async fn write(
65 &self,
66 scope: &Scope,
67 key: &str,
68 value: serde_json::Value,
69 ) -> Result<(), StateError> {
70 let ck = composite_key(scope, key);
71 let mut data = self.data.write().await;
72 data.insert(ck, value);
73 Ok(())
74 }
75
76 async fn delete(&self, scope: &Scope, key: &str) -> Result<(), StateError> {
77 let ck = composite_key(scope, key);
78 let mut data = self.data.write().await;
79 data.remove(&ck);
80 Ok(())
81 }
82
83 async fn list(&self, scope: &Scope, prefix: &str) -> Result<Vec<String>, StateError> {
84 let scope_prefix = serde_json::to_string(scope).unwrap_or_else(|_| "unknown".to_string());
85 let data = self.data.read().await;
86 let keys: Vec<String> = data
87 .keys()
88 .filter_map(|ck| {
89 extract_key(ck, &scope_prefix).and_then(|k| {
90 if k.starts_with(prefix) {
91 Some(k.to_string())
92 } else {
93 None
94 }
95 })
96 })
97 .collect();
98 Ok(keys)
99 }
100
101 async fn search(
102 &self,
103 _scope: &Scope,
104 _query: &str,
105 _limit: usize,
106 ) -> Result<Vec<SearchResult>, StateError> {
107 Ok(vec![])
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use serde_json::json;
116
117 #[tokio::test]
118 async fn write_and_read() {
119 let store = MemoryStore::new();
120 let scope = Scope::Global;
121
122 store.write(&scope, "key1", json!("value1")).await.unwrap();
123 let val = store.read(&scope, "key1").await.unwrap();
124 assert_eq!(val, Some(json!("value1")));
125 }
126
127 #[tokio::test]
128 async fn read_nonexistent_returns_none() {
129 let store = MemoryStore::new();
130 let scope = Scope::Global;
131
132 let val = store.read(&scope, "missing").await.unwrap();
133 assert_eq!(val, None);
134 }
135
136 #[tokio::test]
137 async fn write_overwrites_existing() {
138 let store = MemoryStore::new();
139 let scope = Scope::Global;
140
141 store.write(&scope, "key1", json!("first")).await.unwrap();
142 store.write(&scope, "key1", json!("second")).await.unwrap();
143 let val = store.read(&scope, "key1").await.unwrap();
144 assert_eq!(val, Some(json!("second")));
145 }
146
147 #[tokio::test]
148 async fn delete_removes_key() {
149 let store = MemoryStore::new();
150 let scope = Scope::Global;
151
152 store.write(&scope, "key1", json!("value1")).await.unwrap();
153 store.delete(&scope, "key1").await.unwrap();
154 let val = store.read(&scope, "key1").await.unwrap();
155 assert_eq!(val, None);
156 }
157
158 #[tokio::test]
159 async fn delete_nonexistent_is_ok() {
160 let store = MemoryStore::new();
161 let scope = Scope::Global;
162
163 let result = store.delete(&scope, "missing").await;
164 assert!(result.is_ok());
165 }
166
167 #[tokio::test]
168 async fn list_keys_with_prefix() {
169 let store = MemoryStore::new();
170 let scope = Scope::Global;
171
172 store
173 .write(&scope, "user:name", json!("Alice"))
174 .await
175 .unwrap();
176 store.write(&scope, "user:age", json!(30)).await.unwrap();
177 store
178 .write(&scope, "system:version", json!("1.0"))
179 .await
180 .unwrap();
181
182 let mut keys = store.list(&scope, "user:").await.unwrap();
183 keys.sort();
184 assert_eq!(keys, vec!["user:age", "user:name"]);
185 }
186
187 #[tokio::test]
188 async fn list_empty_prefix_returns_all() {
189 let store = MemoryStore::new();
190 let scope = Scope::Global;
191
192 store.write(&scope, "a", json!(1)).await.unwrap();
193 store.write(&scope, "b", json!(2)).await.unwrap();
194
195 let keys = store.list(&scope, "").await.unwrap();
196 assert_eq!(keys.len(), 2);
197 }
198
199 #[tokio::test]
200 async fn scopes_are_isolated() {
201 let store = MemoryStore::new();
202 let global = Scope::Global;
203 let session = Scope::Session(layer0::SessionId::new("s1"));
204
205 store
206 .write(&global, "key", json!("global_val"))
207 .await
208 .unwrap();
209 store
210 .write(&session, "key", json!("session_val"))
211 .await
212 .unwrap();
213
214 let global_val = store.read(&global, "key").await.unwrap();
215 let session_val = store.read(&session, "key").await.unwrap();
216
217 assert_eq!(global_val, Some(json!("global_val")));
218 assert_eq!(session_val, Some(json!("session_val")));
219 }
220
221 #[tokio::test]
222 async fn search_returns_empty() {
223 let store = MemoryStore::new();
224 let scope = Scope::Global;
225
226 let results = store.search(&scope, "query", 10).await.unwrap();
227 assert!(results.is_empty());
228 }
229
230 #[test]
231 fn default_store_is_empty() {
232 let store = MemoryStore::default();
233 let _ = store; }
235
236 #[test]
237 fn memory_store_implements_state_store() {
238 fn _assert_state_store<T: StateStore>() {}
239 _assert_state_store::<MemoryStore>();
240 }
241}