Skip to main content

state_engine/
load.rs

1use crate::ports::required::{
2    DbClient, EnvClient, KVSClient,
3    InMemoryClient, HttpClient, FileClient,
4};
5use crate::ports::provided::LoadError;
6use crate::core::fixed_bits;
7use serde_json::Value;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11pub struct Load {
12    db: Option<Arc<dyn DbClient>>,
13    kvs: Option<Arc<dyn KVSClient>>,
14    in_memory: Option<Arc<dyn InMemoryClient>>,
15    env: Option<Arc<dyn EnvClient>>,
16    http: Option<Arc<dyn HttpClient>>,
17    file: Option<Arc<dyn FileClient>>,
18}
19
20impl Load {
21    pub fn new() -> Self {
22        Self {
23            db: None,
24            kvs: None,
25            in_memory: None,
26            env: None,
27            http: None,
28            file: None,
29        }
30    }
31
32    pub fn with_db(mut self, client: Arc<dyn DbClient>) -> Self {
33        self.db = Some(client);
34        self
35    }
36
37    pub fn with_kvs(mut self, client: Arc<dyn KVSClient>) -> Self {
38        self.kvs = Some(client);
39        self
40    }
41
42    pub fn with_in_memory(mut self, client: Arc<dyn InMemoryClient>) -> Self {
43        self.in_memory = Some(client);
44        self
45    }
46
47    pub fn with_env(mut self, client: Arc<dyn EnvClient>) -> Self {
48        self.env = Some(client);
49        self
50    }
51
52    pub fn with_http(mut self, client: Arc<dyn HttpClient>) -> Self {
53        self.http = Some(client);
54        self
55    }
56
57    pub fn with_file(mut self, client: Arc<dyn FileClient>) -> Self {
58        self.file = Some(client);
59        self
60    }
61
62    pub fn handle(&self, config: &HashMap<String, Value>) -> Result<Value, LoadError> {
63        let client = config
64            .get("client")
65            .and_then(|v| v.as_u64())
66            .ok_or(LoadError::ConfigMissing("client".into()))?;
67
68        match client {
69            fixed_bits::CLIENT_ENV       => self.load_from_env(config),
70            fixed_bits::CLIENT_IN_MEMORY => self.load_from_in_memory(config),
71            fixed_bits::CLIENT_KVS       => self.load_from_kvs(config),
72            fixed_bits::CLIENT_DB        => self.load_from_db(config),
73            fixed_bits::CLIENT_HTTP      => self.load_from_http(config),
74            fixed_bits::CLIENT_FILE      => self.load_from_file(config),
75            _ => Err(LoadError::ConfigMissing(format!("unsupported client '{}'", client))),
76        }
77    }
78
79    fn load_from_env(
80        &self,
81        config: &HashMap<String, Value>,
82    ) -> Result<Value, LoadError> {
83        let env = self.env.as_deref()
84            .ok_or(LoadError::ClientNotConfigured)?;
85
86        let map = config
87            .get("map")
88            .and_then(|v| v.as_object())
89            .ok_or(LoadError::ConfigMissing("map".into()))?;
90
91        let mut result = serde_json::Map::new();
92        for (config_key, env_key_value) in map {
93            if let Some(env_key) = env_key_value.as_str() {
94                if let Some(value) = env.get(env_key) {
95                    result.insert(config_key.clone(), Value::String(value));
96                }
97            }
98        }
99
100        Ok(Value::Object(result))
101    }
102
103    fn load_from_in_memory(
104        &self,
105        config: &HashMap<String, Value>,
106    ) -> Result<Value, LoadError> {
107        let in_memory = self.in_memory.as_deref()
108            .ok_or(LoadError::ClientNotConfigured)?;
109
110        let key = config
111            .get("key")
112            .and_then(|v| v.as_str())
113            .ok_or(LoadError::ConfigMissing("key".into()))?;
114
115        in_memory
116            .get(key)
117            .ok_or_else(|| LoadError::NotFound(key.into()))
118    }
119
120    fn load_from_kvs(
121        &self,
122        config: &HashMap<String, Value>,
123    ) -> Result<Value, LoadError> {
124        let kvs = self.kvs.as_deref()
125            .ok_or(LoadError::ClientNotConfigured)?;
126
127        let key = config
128            .get("key")
129            .and_then(|v| v.as_str())
130            .ok_or(LoadError::ConfigMissing("key".into()))?;
131
132        let value_str = kvs
133            .get(key)
134            .ok_or_else(|| LoadError::NotFound(key.into()))?;
135
136        serde_json::from_str(&value_str)
137            .map_err(|e| LoadError::ParseError(e.to_string()))
138    }
139
140    fn load_from_db(
141        &self,
142        config: &HashMap<String, Value>,
143    ) -> Result<Value, LoadError> {
144        let db = self.db.as_deref()
145            .ok_or(LoadError::ClientNotConfigured)?;
146
147        let table = config
148            .get("table")
149            .and_then(|v| v.as_str())
150            .ok_or(LoadError::ConfigMissing("table".into()))?;
151
152        let where_clause = config.get("where").and_then(|v| v.as_str());
153
154        let map = config
155            .get("map")
156            .and_then(|v| v.as_object())
157            .ok_or(LoadError::ConfigMissing("map".into()))?;
158
159        let connection = config
160            .get("connection")
161            .ok_or(LoadError::ConfigMissing("connection".into()))?;
162
163        let columns: Vec<&str> = map.values().filter_map(|v| v.as_str()).collect();
164
165        if columns.is_empty() {
166            return Err(LoadError::ConfigMissing("map has no columns".into()));
167        }
168
169        let rows = db
170            .get(connection, table, &columns, where_clause)
171            .ok_or_else(|| LoadError::NotFound(table.into()))?;
172
173        if rows.is_empty() {
174            return Err(LoadError::NotFound(table.into()));
175        }
176
177        let row = &rows[0];
178        let mut result = serde_json::Map::new();
179        for (config_key, db_column_value) in map {
180            if let Some(db_column) = db_column_value.as_str() {
181                if let Some(value) = row.get(db_column) {
182                    result.insert(config_key.clone(), value.clone());
183                }
184            }
185        }
186
187        Ok(Value::Object(result))
188    }
189
190    fn load_from_file(
191        &self,
192        config: &HashMap<String, Value>,
193    ) -> Result<Value, LoadError> {
194        let file = self.file.as_deref()
195            .ok_or(LoadError::ClientNotConfigured)?;
196
197        let key = config
198            .get("key")
199            .and_then(|v| v.as_str())
200            .ok_or(LoadError::ConfigMissing("key".into()))?;
201
202        let content = file
203            .get(key)
204            .ok_or_else(|| LoadError::NotFound(key.into()))?;
205
206        serde_json::from_str(&content)
207            .map_err(|e| LoadError::ParseError(e.to_string()))
208    }
209
210    fn load_from_http(
211        &self,
212        config: &HashMap<String, Value>,
213    ) -> Result<Value, LoadError> {
214        let http = self.http.as_deref()
215            .ok_or(LoadError::ClientNotConfigured)?;
216
217        let url = config
218            .get("url")
219            .and_then(|v| v.as_str())
220            .ok_or(LoadError::ConfigMissing("url".into()))?;
221
222        let headers = config
223            .get("headers")
224            .and_then(|v| v.as_object())
225            .map(|obj| obj.iter()
226                .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
227                .collect::<HashMap<String, String>>());
228
229        let response = http.get(url, headers.as_ref())
230            .ok_or_else(|| LoadError::NotFound(url.into()))?;
231
232        let map = config.get("map").and_then(|v| v.as_object());
233        match map {
234            None => Ok(response),
235            Some(map) => {
236                let row = match &response {
237                    Value::Array(arr) => arr.first()
238                        .ok_or(LoadError::NotFound("empty array response".into()))?,
239                    other => other,
240                };
241                let mut result = serde_json::Map::new();
242                for (config_key, src_key_value) in map {
243                    if let Some(src_key) = src_key_value.as_str() {
244                        if let Some(value) = row.get(src_key) {
245                            result.insert(config_key.clone(), value.clone());
246                        }
247                    }
248                }
249                Ok(Value::Object(result))
250            }
251        }
252    }
253}
254
255impl Default for Load {
256    fn default() -> Self {
257        Self::new()
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    struct MockEnvClient;
266    impl EnvClient for MockEnvClient {
267        fn get(&self, key: &str) -> Option<String> {
268            match key {
269                "Db_HOST" => Some("localhost".to_string()),
270                "Db_PORT" => Some("5432".to_string()),
271                _ => None,
272            }
273        }
274        fn set(&self, _key: &str, _value: String) -> bool { false }
275        fn delete(&self, _key: &str) -> bool { false }
276    }
277
278    struct MockFileClient {
279        store: std::sync::Mutex<HashMap<String, String>>,
280    }
281    impl MockFileClient {
282        fn new(entries: &[(&str, &str)]) -> Self {
283            Self {
284                store: std::sync::Mutex::new(
285                    entries.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()
286                ),
287            }
288        }
289    }
290    impl FileClient for MockFileClient {
291        fn get(&self, key: &str) -> Option<String> {
292            self.store.lock().unwrap().get(key).cloned()
293        }
294        fn set(&self, key: &str, value: String) -> bool {
295            self.store.lock().unwrap().insert(key.to_string(), value);
296            true
297        }
298        fn delete(&self, key: &str) -> bool {
299            self.store.lock().unwrap().remove(key).is_some()
300        }
301    }
302
303    #[test]
304    fn test_load_from_file() {
305        let file = MockFileClient::new(&[("session_data", r#"{"user_id":42}"#)]);
306        let load = Load::new().with_file(Arc::new(file));
307
308        let mut config = HashMap::new();
309        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_FILE.into()));
310        config.insert("key".to_string(), Value::String("session_data".to_string()));
311
312        let result = load.handle(&config).unwrap();
313        assert_eq!(result.get("user_id"), Some(&Value::Number(42.into())));
314    }
315
316    #[test]
317    fn test_load_from_file_key_not_found() {
318        let file = MockFileClient::new(&[]);
319        let load = Load::new().with_file(Arc::new(file));
320
321        let mut config = HashMap::new();
322        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_FILE.into()));
323        config.insert("key".to_string(), Value::String("missing".to_string()));
324
325        assert!(load.handle(&config).is_err());
326    }
327
328    #[test]
329    fn test_load_from_file_client_not_configured() {
330        let load = Load::new();
331
332        let mut config = HashMap::new();
333        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_FILE.into()));
334        config.insert("key".to_string(), Value::String("any".to_string()));
335
336        assert!(load.handle(&config).is_err());
337    }
338
339    // --- InMemory ---
340
341    struct MockInMemory {
342        store: std::sync::Mutex<HashMap<String, Value>>,
343    }
344    impl MockInMemory {
345        fn new(entries: &[(&str, Value)]) -> Self {
346            Self { store: std::sync::Mutex::new(entries.iter().map(|(k, v)| (k.to_string(), v.clone())).collect()) }
347        }
348    }
349    impl InMemoryClient for MockInMemory {
350        fn get(&self, key: &str) -> Option<Value> { self.store.lock().unwrap().get(key).cloned() }
351        fn set(&self, key: &str, value: Value) -> bool { self.store.lock().unwrap().insert(key.to_string(), value); true }
352        fn delete(&self, key: &str) -> bool { self.store.lock().unwrap().remove(key).is_some() }
353    }
354
355    #[test]
356    fn test_load_from_in_memory() {
357        let data = serde_json::json!({"host": "localhost"});
358        let client = Arc::new(MockInMemory::new(&[("conn", data.clone())]));
359        let load = Load::new().with_in_memory(client);
360        let mut config = HashMap::new();
361        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_IN_MEMORY.into()));
362        config.insert("key".to_string(), Value::String("conn".to_string()));
363        assert_eq!(load.handle(&config).unwrap(), data);
364    }
365
366    #[test]
367    fn test_load_from_in_memory_key_not_found() {
368        let client = Arc::new(MockInMemory::new(&[]));
369        let load = Load::new().with_in_memory(client);
370        let mut config = HashMap::new();
371        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_IN_MEMORY.into()));
372        config.insert("key".to_string(), Value::String("missing".to_string()));
373        assert!(load.handle(&config).is_err());
374    }
375
376    #[test]
377    fn test_load_from_in_memory_client_not_configured() {
378        let load = Load::new();
379        let mut config = HashMap::new();
380        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_IN_MEMORY.into()));
381        config.insert("key".to_string(), Value::String("k".to_string()));
382        assert!(load.handle(&config).is_err());
383    }
384
385    // --- KVS ---
386
387    struct MockKVS {
388        store: std::sync::Mutex<HashMap<String, String>>,
389    }
390    impl MockKVS {
391        fn new(entries: &[(&str, &str)]) -> Self {
392            Self { store: std::sync::Mutex::new(entries.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()) }
393        }
394    }
395    impl KVSClient for MockKVS {
396        fn get(&self, key: &str) -> Option<String> { self.store.lock().unwrap().get(key).cloned() }
397        fn set(&self, key: &str, value: String, _: Option<u64>) -> bool { self.store.lock().unwrap().insert(key.to_string(), value); true }
398        fn delete(&self, key: &str) -> bool { self.store.lock().unwrap().remove(key).is_some() }
399    }
400
401    #[test]
402    fn test_load_from_kvs() {
403        let client = Arc::new(MockKVS::new(&[("sess", r#"{"user_id":1}"#)]));
404        let load = Load::new().with_kvs(client);
405        let mut config = HashMap::new();
406        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_KVS.into()));
407        config.insert("key".to_string(), Value::String("sess".to_string()));
408        assert_eq!(load.handle(&config).unwrap().get("user_id"), Some(&Value::Number(1.into())));
409    }
410
411    #[test]
412    fn test_load_from_kvs_key_not_found() {
413        let client = Arc::new(MockKVS::new(&[]));
414        let load = Load::new().with_kvs(client);
415        let mut config = HashMap::new();
416        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_KVS.into()));
417        config.insert("key".to_string(), Value::String("missing".to_string()));
418        assert!(load.handle(&config).is_err());
419    }
420
421    #[test]
422    fn test_load_from_kvs_client_not_configured() {
423        let load = Load::new();
424        let mut config = HashMap::new();
425        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_KVS.into()));
426        config.insert("key".to_string(), Value::String("k".to_string()));
427        assert!(load.handle(&config).is_err());
428    }
429
430    // --- DB ---
431
432    struct MockDb {
433        rows: Vec<HashMap<String, Value>>,
434    }
435    impl MockDb {
436        fn new(rows: Vec<HashMap<String, Value>>) -> Self { Self { rows } }
437    }
438    impl DbClient for MockDb {
439        fn get(&self, _conn: &Value, _table: &str, _cols: &[&str], _where: Option<&str>) -> Option<Vec<HashMap<String, Value>>> {
440            if self.rows.is_empty() { None } else { Some(self.rows.clone()) }
441        }
442        fn set(&self, _: &Value, _: &str, _: &HashMap<String, Value>, _: Option<&str>) -> bool { false }
443        fn delete(&self, _: &Value, _: &str, _: Option<&str>) -> bool { false }
444    }
445
446    fn db_config(table: &str, map: &[(&str, &str)]) -> HashMap<String, Value> {
447        let mut config = HashMap::new();
448        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_DB.into()));
449        config.insert("table".to_string(), Value::String(table.to_string()));
450        config.insert("connection".to_string(), Value::Object(serde_json::Map::new()));
451        let mut map_obj = serde_json::Map::new();
452        for (k, v) in map { map_obj.insert(k.to_string(), Value::String(v.to_string())); }
453        config.insert("map".to_string(), Value::Object(map_obj));
454        config
455    }
456
457    #[test]
458    fn test_load_from_db() {
459        let mut row = HashMap::new();
460        row.insert("id".to_string(), Value::Number(42.into()));
461        let client = Arc::new(MockDb::new(vec![row]));
462        let load = Load::new().with_db(client);
463        let config = db_config("users", &[("id", "id")]);
464        assert_eq!(load.handle(&config).unwrap().get("id"), Some(&Value::Number(42.into())));
465    }
466
467    #[test]
468    fn test_load_from_db_no_rows() {
469        let client = Arc::new(MockDb::new(vec![]));
470        let load = Load::new().with_db(client);
471        let config = db_config("users", &[("id", "id")]);
472        assert!(load.handle(&config).is_err());
473    }
474
475    #[test]
476    fn test_load_from_db_client_not_configured() {
477        let load = Load::new();
478        let config = db_config("users", &[("id", "id")]);
479        assert!(load.handle(&config).is_err());
480    }
481
482    // --- HTTP ---
483
484    struct MockHttp {
485        response: Option<Value>,
486    }
487    impl MockHttp {
488        fn new(response: Option<Value>) -> Self { Self { response } }
489    }
490    impl crate::ports::required::HttpClient for MockHttp {
491        fn get(&self, _: &str, _: Option<&HashMap<String, String>>) -> Option<Value> { self.response.clone() }
492        fn set(&self, _: &str, _: Value, _: Option<&HashMap<String, String>>) -> bool { false }
493        fn delete(&self, _: &str, _: Option<&HashMap<String, String>>) -> bool { false }
494    }
495
496    fn http_config(url: &str) -> HashMap<String, Value> {
497        let mut c = HashMap::new();
498        c.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_HTTP.into()));
499        c.insert("url".to_string(), Value::String(url.to_string()));
500        c
501    }
502
503    #[test]
504    fn test_load_from_http_no_map() {
505        let client = Arc::new(MockHttp::new(Some(serde_json::json!({"status": "ok"}))));
506        let load = Load::new().with_http(client);
507        let config = http_config("http://example.com/health");
508        assert_eq!(load.handle(&config).unwrap(), serde_json::json!({"status": "ok"}));
509    }
510
511    #[test]
512    fn test_load_from_http_with_map() {
513        let client = Arc::new(MockHttp::new(Some(serde_json::json!({"status": "ok"}))));
514        let load = Load::new().with_http(client);
515        let mut config = http_config("http://example.com/health");
516        let mut map = serde_json::Map::new();
517        map.insert("health".to_string(), Value::String("status".to_string()));
518        config.insert("map".to_string(), Value::Object(map));
519        let result = load.handle(&config).unwrap();
520        assert_eq!(result.get("health"), Some(&Value::String("ok".to_string())));
521    }
522
523    #[test]
524    fn test_load_from_http_not_found() {
525        let client = Arc::new(MockHttp::new(None));
526        let load = Load::new().with_http(client);
527        let config = http_config("http://example.com/health");
528        assert!(load.handle(&config).is_err());
529    }
530
531    #[test]
532    fn test_load_from_http_client_not_configured() {
533        let load = Load::new();
534        let config = http_config("http://example.com/health");
535        assert!(load.handle(&config).is_err());
536    }
537
538    #[test]
539    fn test_load_from_env() {
540        let env = MockEnvClient;
541        let load = Load::new().with_env(Arc::new(env));
542
543        let mut config = HashMap::new();
544        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_ENV.into()));
545
546        let mut map = serde_json::Map::new();
547        map.insert("host".to_string(), Value::String("Db_HOST".to_string()));
548        map.insert("port".to_string(), Value::String("Db_PORT".to_string()));
549        config.insert("map".to_string(), Value::Object(map));
550
551        let result = load.handle(&config).unwrap();
552
553        assert_eq!(result.get("host"), Some(&Value::String("localhost".to_string())));
554        assert_eq!(result.get("port"), Some(&Value::String("5432".to_string())));
555    }
556}