Skip to main content

state_engine/
store.rs

1use crate::ports::required::{InMemoryClient, KVSClient, HttpClient, FileClient};
2use crate::ports::provided::StoreError;
3use crate::core::fixed_bits;
4use serde_json::Value;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8pub struct Store {
9    in_memory: Option<Arc<dyn InMemoryClient>>,
10    kvs: Option<Arc<dyn KVSClient>>,
11    http: Option<Arc<dyn HttpClient>>,
12    file: Option<Arc<dyn FileClient>>,
13}
14
15impl Store {
16    pub fn new() -> Self {
17        Self {
18            in_memory: None,
19            kvs: None,
20            http: None,
21            file: None,
22        }
23    }
24
25    pub fn with_in_memory(mut self, client: Arc<dyn InMemoryClient>) -> Self {
26        self.in_memory = Some(client);
27        self
28    }
29
30    pub fn with_kvs(mut self, client: Arc<dyn KVSClient>) -> Self {
31        self.kvs = Some(client);
32        self
33    }
34
35    pub fn with_http(mut self, client: Arc<dyn HttpClient>) -> Self {
36        self.http = Some(client);
37        self
38    }
39
40    pub fn with_file(mut self, client: Arc<dyn FileClient>) -> Self {
41        self.file = Some(client);
42        self
43    }
44
45    pub fn get(&self, store_config: &HashMap<String, Value>) -> Option<Value> {
46        let client = store_config.get("client")?.as_u64()?;
47
48        match client {
49            fixed_bits::CLIENT_IN_MEMORY => {
50                let in_memory = self.in_memory.as_deref()?;
51                let key = store_config.get("key")?.as_str()?;
52                in_memory.get(key)
53            }
54            fixed_bits::CLIENT_KVS => {
55                let kvs = self.kvs.as_deref()?;
56                let key = store_config.get("key")?.as_str()?;
57                let value_str = kvs.get(key)?;
58                serde_json::from_str(&value_str).ok()
59            }
60            fixed_bits::CLIENT_HTTP => {
61                let http = self.http.as_deref()?;
62                let url = store_config.get("url")?.as_str()?;
63                let headers = store_config
64                    .get("headers")
65                    .and_then(|v| v.as_object())
66                    .map(|obj| obj.iter()
67                        .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
68                        .collect::<HashMap<String, String>>());
69                http.get(url, headers.as_ref())
70            }
71            fixed_bits::CLIENT_FILE => {
72                let file = self.file.as_deref()?;
73                let key = store_config.get("key")?.as_str()?;
74                let content = file.get(key)?;
75                serde_json::from_str(&content).ok()
76            }
77            _ => None,
78        }
79    }
80
81    pub fn set(
82        &self,
83        store_config: &HashMap<String, Value>,
84        value: Value,
85        ttl: Option<u64>,
86    ) -> Result<bool, StoreError> {
87        let client = store_config
88            .get("client")
89            .and_then(|v| v.as_u64())
90            .ok_or(StoreError::ConfigMissing("client".into()))?;
91
92        match client {
93            fixed_bits::CLIENT_IN_MEMORY => {
94                let in_memory = self.in_memory.as_deref()
95                    .ok_or(StoreError::ClientNotConfigured)?;
96                let key = store_config.get("key").and_then(|v| v.as_str())
97                    .ok_or(StoreError::ConfigMissing("key".into()))?;
98                Ok(in_memory.set(key, value))
99            }
100            fixed_bits::CLIENT_KVS => {
101                let kvs = self.kvs.as_deref()
102                    .ok_or(StoreError::ClientNotConfigured)?;
103                let key = store_config.get("key").and_then(|v| v.as_str())
104                    .ok_or(StoreError::ConfigMissing("key".into()))?;
105                let serialized = serde_json::to_string(&value)
106                    .map_err(|e| StoreError::SerializeError(e.to_string()))?;
107                let final_ttl = ttl.or_else(|| store_config.get("ttl").and_then(|v| v.as_u64()));
108                Ok(kvs.set(key, serialized, final_ttl))
109            }
110            fixed_bits::CLIENT_HTTP => {
111                let http = self.http.as_deref()
112                    .ok_or(StoreError::ClientNotConfigured)?;
113                let url = store_config.get("url").and_then(|v| v.as_str())
114                    .ok_or(StoreError::ConfigMissing("url".into()))?;
115                let headers = store_config
116                    .get("headers")
117                    .and_then(|v| v.as_object())
118                    .map(|obj| obj.iter()
119                        .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
120                        .collect::<HashMap<String, String>>());
121                Ok(http.set(url, value, headers.as_ref()))
122            }
123            fixed_bits::CLIENT_FILE => {
124                let file = self.file.as_deref()
125                    .ok_or(StoreError::ClientNotConfigured)?;
126                let key = store_config.get("key").and_then(|v| v.as_str())
127                    .ok_or(StoreError::ConfigMissing("key".into()))?;
128                let serialized = serde_json::to_string(&value)
129                    .map_err(|e| StoreError::SerializeError(e.to_string()))?;
130                Ok(file.set(key, serialized))
131            }
132            _ => Err(StoreError::UnsupportedClient(client)),
133        }
134    }
135
136    pub fn delete(&self, store_config: &HashMap<String, Value>) -> Result<bool, StoreError> {
137        let client = store_config
138            .get("client")
139            .and_then(|v| v.as_u64())
140            .ok_or(StoreError::ConfigMissing("client".into()))?;
141
142        match client {
143            fixed_bits::CLIENT_IN_MEMORY => {
144                let in_memory = self.in_memory.as_deref()
145                    .ok_or(StoreError::ClientNotConfigured)?;
146                let key = store_config.get("key").and_then(|v| v.as_str())
147                    .ok_or(StoreError::ConfigMissing("key".into()))?;
148                Ok(in_memory.delete(key))
149            }
150            fixed_bits::CLIENT_KVS => {
151                let kvs = self.kvs.as_deref()
152                    .ok_or(StoreError::ClientNotConfigured)?;
153                let key = store_config.get("key").and_then(|v| v.as_str())
154                    .ok_or(StoreError::ConfigMissing("key".into()))?;
155                Ok(kvs.delete(key))
156            }
157            fixed_bits::CLIENT_HTTP => {
158                let http = self.http.as_deref()
159                    .ok_or(StoreError::ClientNotConfigured)?;
160                let url = store_config.get("url").and_then(|v| v.as_str())
161                    .ok_or(StoreError::ConfigMissing("url".into()))?;
162                let headers = store_config
163                    .get("headers")
164                    .and_then(|v| v.as_object())
165                    .map(|obj| obj.iter()
166                        .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
167                        .collect::<HashMap<String, String>>());
168                Ok(http.delete(url, headers.as_ref()))
169            }
170            fixed_bits::CLIENT_FILE => {
171                let file = self.file.as_deref()
172                    .ok_or(StoreError::ClientNotConfigured)?;
173                let key = store_config.get("key").and_then(|v| v.as_str())
174                    .ok_or(StoreError::ConfigMissing("key".into()))?;
175                Ok(file.delete(key))
176            }
177            _ => Err(StoreError::UnsupportedClient(client)),
178        }
179    }
180}
181
182impl Default for Store {
183    fn default() -> Self {
184        Self::new()
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::core::fixed_bits;
192
193    struct MockFileClient {
194        store: std::sync::Mutex<std::collections::HashMap<String, String>>,
195    }
196    impl MockFileClient {
197        fn new() -> Self {
198            Self { store: std::sync::Mutex::new(std::collections::HashMap::new()) }
199        }
200    }
201    impl FileClient for MockFileClient {
202        fn get(&self, key: &str) -> Option<String> {
203            self.store.lock().unwrap().get(key).cloned()
204        }
205        fn set(&self, key: &str, value: String) -> bool {
206            self.store.lock().unwrap().insert(key.to_string(), value);
207            true
208        }
209        fn delete(&self, key: &str) -> bool {
210            self.store.lock().unwrap().remove(key).is_some()
211        }
212    }
213
214    fn file_config(key: &str) -> HashMap<String, Value> {
215        let mut config = HashMap::new();
216        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_FILE.into()));
217        config.insert("key".to_string(), Value::String(key.to_string()));
218        config
219    }
220
221    #[test]
222    fn test_store_file_set_and_get() {
223        let file = Arc::new(MockFileClient::new());
224        let store = Store::new().with_file(file);
225        let config = file_config("my_key");
226
227        assert_eq!(store.set(&config, serde_json::json!({"x": 1}), None).unwrap(), true);
228        let result = store.get(&config).unwrap();
229        assert_eq!(result, serde_json::json!({"x": 1}));
230    }
231
232    #[test]
233    fn test_store_file_delete() {
234        let file = Arc::new(MockFileClient::new());
235        let store = Store::new().with_file(file);
236        let config = file_config("my_key");
237
238        store.set(&config, serde_json::json!(1), None).unwrap();
239        assert_eq!(store.delete(&config).unwrap(), true);
240        assert!(store.get(&config).is_none());
241    }
242
243    #[test]
244    fn test_store_file_client_not_configured() {
245        let store = Store::new();
246        let config = file_config("my_key");
247
248        assert!(store.set(&config, serde_json::json!(1), None).is_err());
249        assert!(store.delete(&config).is_err());
250    }
251
252    // --- InMemory ---
253
254    struct MockInMemory {
255        store: std::sync::Mutex<std::collections::HashMap<String, Value>>,
256    }
257    impl MockInMemory {
258        fn new() -> Self { Self { store: std::sync::Mutex::new(std::collections::HashMap::new()) } }
259    }
260    impl InMemoryClient for MockInMemory {
261        fn get(&self, key: &str) -> Option<Value> { self.store.lock().unwrap().get(key).cloned() }
262        fn set(&self, key: &str, value: Value) -> bool { self.store.lock().unwrap().insert(key.to_string(), value); true }
263        fn delete(&self, key: &str) -> bool { self.store.lock().unwrap().remove(key).is_some() }
264    }
265
266    fn in_memory_config(key: &str) -> HashMap<String, Value> {
267        let mut c = HashMap::new();
268        c.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_IN_MEMORY.into()));
269        c.insert("key".to_string(), Value::String(key.to_string()));
270        c
271    }
272
273    #[test]
274    fn test_store_in_memory_set_and_get() {
275        let client = Arc::new(MockInMemory::new());
276        let store = Store::new().with_in_memory(client);
277        let config = in_memory_config("k");
278        assert!(store.set(&config, serde_json::json!(42), None).unwrap());
279        assert_eq!(store.get(&config).unwrap(), serde_json::json!(42));
280    }
281
282    #[test]
283    fn test_store_in_memory_delete() {
284        let client = Arc::new(MockInMemory::new());
285        let store = Store::new().with_in_memory(client);
286        let config = in_memory_config("k");
287        store.set(&config, serde_json::json!(1), None).unwrap();
288        assert!(store.delete(&config).unwrap());
289        assert!(store.get(&config).is_none());
290    }
291
292    #[test]
293    fn test_store_in_memory_client_not_configured() {
294        let store = Store::new();
295        let config = in_memory_config("k");
296        assert!(store.set(&config, serde_json::json!(1), None).is_err());
297        assert!(store.delete(&config).is_err());
298    }
299
300    // --- KVS ---
301
302    struct MockKVS {
303        store: std::sync::Mutex<std::collections::HashMap<String, String>>,
304    }
305    impl MockKVS {
306        fn new() -> Self { Self { store: std::sync::Mutex::new(std::collections::HashMap::new()) } }
307    }
308    impl KVSClient for MockKVS {
309        fn get(&self, key: &str) -> Option<String> { self.store.lock().unwrap().get(key).cloned() }
310        fn set(&self, key: &str, value: String, _ttl: Option<u64>) -> bool { self.store.lock().unwrap().insert(key.to_string(), value); true }
311        fn delete(&self, key: &str) -> bool { self.store.lock().unwrap().remove(key).is_some() }
312    }
313
314    fn kvs_config(key: &str) -> HashMap<String, Value> {
315        let mut c = HashMap::new();
316        c.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_KVS.into()));
317        c.insert("key".to_string(), Value::String(key.to_string()));
318        c
319    }
320
321    #[test]
322    fn test_store_kvs_set_and_get() {
323        let client = Arc::new(MockKVS::new());
324        let store = Store::new().with_kvs(client);
325        let config = kvs_config("k");
326        assert!(store.set(&config, serde_json::json!({"v": 1}), None).unwrap());
327        assert_eq!(store.get(&config).unwrap(), serde_json::json!({"v": 1}));
328    }
329
330    #[test]
331    fn test_store_kvs_set_uses_ttl_from_config() {
332        let client = Arc::new(MockKVS::new());
333        let store = Store::new().with_kvs(client);
334        let mut config = kvs_config("k");
335        config.insert("ttl".to_string(), Value::Number(3600.into()));
336        assert!(store.set(&config, serde_json::json!(1), None).unwrap());
337    }
338
339    #[test]
340    fn test_store_kvs_delete() {
341        let client = Arc::new(MockKVS::new());
342        let store = Store::new().with_kvs(client);
343        let config = kvs_config("k");
344        store.set(&config, serde_json::json!(1), None).unwrap();
345        assert!(store.delete(&config).unwrap());
346        assert!(store.get(&config).is_none());
347    }
348
349    #[test]
350    fn test_store_kvs_client_not_configured() {
351        let store = Store::new();
352        let config = kvs_config("k");
353        assert!(store.set(&config, serde_json::json!(1), None).is_err());
354        assert!(store.delete(&config).is_err());
355    }
356
357    // --- HTTP ---
358
359    struct MockHttp {
360        store: std::sync::Mutex<std::collections::HashMap<String, Value>>,
361    }
362    impl MockHttp {
363        fn new() -> Self { Self { store: std::sync::Mutex::new(std::collections::HashMap::new()) } }
364    }
365    impl crate::ports::required::HttpClient for MockHttp {
366        fn get(&self, url: &str, _: Option<&HashMap<String, String>>) -> Option<Value> {
367            self.store.lock().unwrap().get(url).cloned()
368        }
369        fn set(&self, url: &str, value: Value, _: Option<&HashMap<String, String>>) -> bool {
370            self.store.lock().unwrap().insert(url.to_string(), value); true
371        }
372        fn delete(&self, url: &str, _: Option<&HashMap<String, String>>) -> bool {
373            self.store.lock().unwrap().remove(url).is_some()
374        }
375    }
376
377    fn http_config(url: &str) -> HashMap<String, Value> {
378        let mut c = HashMap::new();
379        c.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_HTTP.into()));
380        c.insert("url".to_string(), Value::String(url.to_string()));
381        c
382    }
383
384    #[test]
385    fn test_store_http_set_and_get() {
386        let client = Arc::new(MockHttp::new());
387        let store = Store::new().with_http(client);
388        let config = http_config("http://example.com/data");
389        assert!(store.set(&config, serde_json::json!({"x": 1}), None).unwrap());
390        assert_eq!(store.get(&config).unwrap(), serde_json::json!({"x": 1}));
391    }
392
393    #[test]
394    fn test_store_http_delete() {
395        let client = Arc::new(MockHttp::new());
396        let store = Store::new().with_http(client);
397        let config = http_config("http://example.com/data");
398        store.set(&config, serde_json::json!(1), None).unwrap();
399        assert!(store.delete(&config).unwrap());
400        assert!(store.get(&config).is_none());
401    }
402
403    #[test]
404    fn test_store_http_client_not_configured() {
405        let store = Store::new();
406        let config = http_config("http://example.com/data");
407        assert!(store.set(&config, serde_json::json!(1), None).is_err());
408        assert!(store.delete(&config).is_err());
409    }
410}