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 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 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 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}