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