Skip to main content

state_engine/
load.rs

1use crate::ports::required::{
2    DbClient, EnvClient, KVSClient,
3    InMemoryClient, HttpClient,
4};
5use crate::core::fixed_bits;
6use serde_json::Value;
7use std::collections::HashMap;
8
9pub struct Load<'a> {
10    db: Option<&'a dyn DbClient>,
11    kvs: Option<&'a dyn KVSClient>,
12    in_memory: Option<&'a dyn InMemoryClient>,
13    env: Option<&'a dyn EnvClient>,
14    http: Option<&'a dyn HttpClient>,
15}
16
17impl<'a> Load<'a> {
18    pub fn new() -> Self {
19        Self {
20            db: None,
21            kvs: None,
22            in_memory: None,
23            env: None,
24            http: None,
25        }
26    }
27
28    pub fn with_db(mut self, client: &'a dyn DbClient) -> Self {
29        self.db = Some(client);
30        self
31    }
32
33    pub fn with_kvs(mut self, client: &'a dyn KVSClient) -> Self {
34        self.kvs = Some(client);
35        self
36    }
37
38    pub fn with_in_memory(mut self, client: &'a dyn InMemoryClient) -> Self {
39        self.in_memory = Some(client);
40        self
41    }
42
43    pub fn with_env(mut self, client: &'a dyn EnvClient) -> Self {
44        self.env = Some(client);
45        self
46    }
47
48    pub fn with_http(mut self, client: &'a dyn HttpClient) -> Self {
49        self.http = Some(client);
50        self
51    }
52
53    pub fn handle(&self, config: &HashMap<String, Value>) -> Result<Value, String> {
54        let client = config
55            .get("client")
56            .and_then(|v| v.as_u64())
57            .ok_or("Load::handle: 'client' not found in _load config")?;
58
59        match client {
60            fixed_bits::CLIENT_ENV       => self.load_from_env(config),
61            fixed_bits::CLIENT_IN_MEMORY => self.load_from_in_memory(config),
62            fixed_bits::CLIENT_KVS       => self.load_from_kvs(config),
63            fixed_bits::CLIENT_DB        => self.load_from_db(config),
64            fixed_bits::CLIENT_HTTP      => self.load_from_http(config),
65            _ => Err(format!("Load::handle: unsupported client '{}'", client)),
66        }
67    }
68
69    fn load_from_env(
70        &self,
71        config: &HashMap<String, Value>,
72    ) -> Result<Value, String> {
73        let env = self
74            .env
75            .ok_or("Load::load_from_env: EnvClient not configured")?;
76
77        let map = config
78            .get("map")
79            .and_then(|v| v.as_object())
80            .ok_or("Load::load_from_env: 'map' not found")?;
81
82        let mut result = serde_json::Map::new();
83
84        for (config_key, env_key_value) in map {
85            if let Some(env_key) = env_key_value.as_str() {
86                if let Some(value) = env.get(env_key) {
87                    result.insert(config_key.clone(), Value::String(value));
88                }
89            }
90        }
91
92        Ok(Value::Object(result))
93    }
94
95    fn load_from_in_memory(
96        &self,
97        config: &HashMap<String, Value>,
98    ) -> Result<Value, String> {
99        let in_memory = self
100            .in_memory
101            .ok_or("Load::load_from_in_memory: InMemoryClient not configured")?;
102
103        let key = config
104            .get("key")
105            .and_then(|v| v.as_str())
106            .ok_or("Load::load_from_in_memory: 'key' not found")?;
107
108        in_memory
109            .get(key)
110            .ok_or_else(|| format!("Load::load_from_in_memory: key '{}' not found", key))
111    }
112
113    fn load_from_kvs(
114        &self,
115        config: &HashMap<String, Value>,
116    ) -> Result<Value, String> {
117        let kvs = self
118            .kvs
119            .ok_or("Load::load_from_kvs: KVSClient not configured")?;
120
121        let key = config
122            .get("key")
123            .and_then(|v| v.as_str())
124            .ok_or("Load::load_from_kvs: 'key' not found")?;
125
126        let value_str = kvs
127            .get(key)
128            .ok_or_else(|| format!("Load::load_from_kvs: key '{}' not found", key))?;
129
130        serde_json::from_str(&value_str)
131            .map_err(|e| format!("Load::load_from_kvs: JSON parse error: {}", e))
132    }
133
134    fn load_from_db(
135        &self,
136        config: &HashMap<String, Value>,
137    ) -> Result<Value, String> {
138        let db = self
139            .db
140            .ok_or("Load::load_from_db: DbClient not configured")?;
141
142        let table = config
143            .get("table")
144            .and_then(|v| v.as_str())
145            .ok_or("Load::load_from_db: 'table' not found")?;
146
147        let where_clause = config.get("where").and_then(|v| v.as_str());
148
149        let map = config
150            .get("map")
151            .and_then(|v| v.as_object())
152            .ok_or("Load::load_from_db: 'map' not found")?;
153
154        let connection = config
155            .get("connection")
156            .ok_or("Load::load_from_db: 'connection' not specified")?;
157
158        let columns: Vec<&str> = map
159            .values()
160            .filter_map(|v| v.as_str())
161            .collect();
162
163        if columns.is_empty() {
164            return Err("Load::load_from_db: no columns specified in map".to_string());
165        }
166
167        let rows = db
168            .get(connection, table, &columns, where_clause)
169            .ok_or_else(|| format!("Load::load_from_db: fetch failed for table '{}'", table))?;
170
171        if rows.is_empty() {
172            return Err(format!("Load::load_from_db: no data found in table '{}'", table));
173        }
174
175        let row: &HashMap<String, Value> = &rows[0];
176
177        let mut result = serde_json::Map::new();
178        for (config_key, db_column_value) in map {
179            if let Some(db_column) = db_column_value.as_str() {
180                if let Some(value) = row.get(db_column) {
181                    result.insert(config_key.clone(), value.clone());
182                }
183            }
184        }
185
186        Ok(Value::Object(result))
187    }
188
189    fn load_from_http(
190        &self,
191        config: &HashMap<String, Value>,
192    ) -> Result<Value, String> {
193        let http = self
194            .http
195            .ok_or("Load::load_from_http: HttpClient not configured")?;
196
197        let url = config
198            .get("url")
199            .and_then(|v| v.as_str())
200            .ok_or("Load::load_from_http: 'url' not found")?;
201
202        let headers = config
203            .get("headers")
204            .and_then(|v| v.as_object())
205            .map(|obj| obj.iter()
206                .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
207                .collect::<HashMap<String, String>>());
208
209        let response = http.get(url, headers.as_ref())
210            .ok_or_else(|| format!("Load::load_from_http: GET '{}' failed", url))?;
211
212        let map = config.get("map").and_then(|v| v.as_object());
213        match map {
214            None => Ok(response),
215            Some(map) => {
216                let row = match &response {
217                    Value::Array(arr) => arr.first()
218                        .ok_or_else(|| "Load::load_from_http: empty array response".to_string())?,
219                    other => other,
220                };
221                let mut result = serde_json::Map::new();
222                for (config_key, src_key_value) in map {
223                    if let Some(src_key) = src_key_value.as_str() {
224                        if let Some(value) = row.get(src_key) {
225                            result.insert(config_key.clone(), value.clone());
226                        }
227                    }
228                }
229                Ok(Value::Object(result))
230            }
231        }
232    }
233}
234
235impl<'a> Default for Load<'a> {
236    fn default() -> Self {
237        Self::new()
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    struct MockEnvClient;
246    impl EnvClient for MockEnvClient {
247        fn get(&self, key: &str) -> Option<String> {
248            match key {
249                "Db_HOST" => Some("localhost".to_string()),
250                "Db_PORT" => Some("5432".to_string()),
251                _ => None,
252            }
253        }
254        fn set(&self, _key: &str, _value: String) -> bool { false }
255        fn delete(&self, _key: &str) -> bool { false }
256    }
257
258    #[test]
259    fn test_load_from_env() {
260        let env = MockEnvClient;
261        let load = Load::new().with_env(&env);
262
263        let mut config = HashMap::new();
264        config.insert("client".to_string(), Value::Number(fixed_bits::CLIENT_ENV.into()));
265
266        let mut map = serde_json::Map::new();
267        map.insert("host".to_string(), Value::String("Db_HOST".to_string()));
268        map.insert("port".to_string(), Value::String("Db_PORT".to_string()));
269        config.insert("map".to_string(), Value::Object(map));
270
271        let result = load.handle(&config).unwrap();
272
273        assert_eq!(result.get("host"), Some(&Value::String("localhost".to_string())));
274        assert_eq!(result.get("port"), Some(&Value::String("5432".to_string())));
275    }
276}