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}