better_config_loader/
yml.rs

1use better_config_core::{AbstractConfig, Error};
2use std::collections::HashMap;
3use std::fs;
4
5/// Indicates that structure can be initialized from a YAML/YML file.
6pub trait YmlConfig<T = HashMap<String, String>>: AbstractConfig<T> {
7    /// Load specified YAML/YML file and initialize the structure.
8    ///
9    /// # Arguments
10    /// * `target` - Path to the YAML/YML file.
11    ///
12    /// # Errors
13    /// * `Error::LoadFileError` - If the specified YAML/YML file cannot be loaded or parsed.
14    fn load(target: Option<String>) -> Result<T, Error>
15    where
16        T: Default,
17        HashMap<String, String>: Into<T>,
18        Self: Sized,
19    {
20        let target = target.or(Some("config.yml".to_string()));
21
22        let mut yaml_map = HashMap::new();
23
24        if let Some(target) = target {
25            let file_paths: Vec<String> = target
26                .split(',')
27                .map(|s| s.trim().to_string())
28                .filter(|s| !s.is_empty())
29                .collect();
30
31            for file_path in file_paths {
32                let content = fs::read_to_string(&file_path).map_err(|e| Error::LoadFileError {
33                    name: file_path.clone(),
34                    source: Some(Box::new(e)),
35                })?;
36
37                let value: serde_yml::Value = serde_yml::from_str(&content).map_err(|e| Error::LoadFileError {
38                    name: file_path.clone(),
39                    source: Some(Box::new(e)),
40                })?;
41
42                flatten_yml_value(&value, None, &mut yaml_map);
43            }
44        }
45
46        Ok(yaml_map.into())
47    }
48}
49
50fn flatten_yml_value(value: &serde_yml::Value, parent_key: Option<String>, map: &mut HashMap<String, String>) {
51    match value {
52        serde_yml::Value::Mapping(obj) => {
53            for (key, val) in obj {
54                let key_str = match key {
55                    serde_yml::Value::String(s) => s.clone(),
56                    _ => serde_yml::to_string(key).unwrap_or_default(),
57                };
58                let new_key = match &parent_key {
59                    Some(parent) => format!("{}.{}", parent, key_str),
60                    None => key_str,
61                };
62                flatten_yml_value(val, Some(new_key), map);
63            }
64        }
65        serde_yml::Value::Sequence(arr) => {
66            for (i, val) in arr.iter().enumerate() {
67                let new_key = match &parent_key {
68                    Some(parent) => format!("{}[{}]", parent, i),
69                    None => i.to_string(),
70                };
71                flatten_yml_value(val, Some(new_key), map);
72            }
73        }
74        serde_yml::Value::String(s) => {
75            if let Some(key) = parent_key {
76                map.insert(key, s.to_string());
77            }
78        }
79        serde_yml::Value::Number(n) => {
80            if let Some(key) = parent_key {
81                if n.is_i64() {
82                    map.insert(key, n.as_i64().unwrap().to_string());
83                } else if n.is_f64() {
84                    map.insert(key, n.as_f64().unwrap().to_string());
85                } else {
86                    map.insert(key, n.to_string());
87                }
88            }
89        }
90        serde_yml::Value::Bool(b) => {
91            if let Some(key) = parent_key {
92                map.insert(key, b.to_string());
93            }
94        }
95        serde_yml::Value::Null => {}
96        _ => {}
97    }
98}