better_config_loader/
yml.rs1use better_config_core::{merge_with_env_uppercase, AbstractConfig, Error, misc};
2use std::collections::{HashMap, HashSet};
3use std::fs;
4
5pub trait YmlConfig<T = HashMap<String, String>>: AbstractConfig<T> {
7 fn load(target: Option<String>) -> Result<T, Error>
15 where
16 T: Default,
17 HashMap<String, String>: Into<T>,
18 Self: Sized,
19 {
20 Self::load_with_override(target, &HashSet::new())
21 }
22
23 fn load_with_override(target: Option<String>, excluded_keys: &HashSet<String>) -> Result<T, Error>
32 where
33 T: Default,
34 HashMap<String, String>: Into<T>,
35 Self: Sized,
36 {
37 let target = target.or(Some("config.yml".to_string()));
38
39 let mut yaml_map = HashMap::new();
40
41 if let Some(target) = target {
42 let file_paths = misc::validate_and_split_paths(&target)?;
43
44 for file_path in file_paths {
45 misc::check_file_accessibility(&file_path)?;
47
48 let content = fs::read_to_string(&file_path)
49 .map_err(|e| Error::IoError {
50 operation: format!("read file '{}'", file_path),
51 source: Some(Box::new(e)),
52 })?;
53
54 let value: serde_yml::Value = serde_yml::from_str(&content)
55 .map_err(|e| Error::parse_yaml_error(&file_path, e))?;
56
57 flatten_yml_value(&value, None, &mut yaml_map)
58 .map_err(|e| Error::value_conversion_error("yaml", "string", &format!("{}", e)))?;
59 }
60 }
61
62 let yaml_map = merge_with_env_uppercase(yaml_map, None, excluded_keys);
64
65 Ok(yaml_map.into())
66 }
67}
68
69fn flatten_yml_value(value: &serde_yml::Value, parent_key: Option<String>, map: &mut HashMap<String, String>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
70 match value {
71 serde_yml::Value::Mapping(obj) => {
72 for (key, val) in obj {
73 let key_str = match key {
74 serde_yml::Value::String(s) => s.clone(),
75 _ => serde_yml::to_string(key).unwrap_or_else(|_| "unknown".to_string()),
76 };
77 let new_key = match &parent_key {
78 Some(parent) => format!("{}.{}", parent, key_str),
79 None => key_str,
80 };
81 flatten_yml_value(val, Some(new_key), map)?;
82 }
83 }
84 serde_yml::Value::Sequence(arr) => {
85 for (i, val) in arr.iter().enumerate() {
86 let new_key = match &parent_key {
87 Some(parent) => format!("{}[{}]", parent, i),
88 None => i.to_string(),
89 };
90 flatten_yml_value(val, Some(new_key), map)?;
91 }
92 }
93 serde_yml::Value::String(s) => {
94 if let Some(key) = parent_key {
95 map.insert(key, s.to_string());
96 }
97 }
98 serde_yml::Value::Number(n) => {
99 if let Some(key) = parent_key {
100 let num_str = if n.is_i64() {
101 n.as_i64()
102 .map(|v| v.to_string())
103 .unwrap_or_else(|| n.to_string())
104 } else if n.is_f64() {
105 n.as_f64()
106 .map(|v| v.to_string())
107 .unwrap_or_else(|| n.to_string())
108 } else {
109 n.to_string()
110 };
111 map.insert(key, num_str);
112 }
113 }
114 serde_yml::Value::Bool(b) => {
115 if let Some(key) = parent_key {
116 map.insert(key, b.to_string());
117 }
118 }
119 serde_yml::Value::Null => {}
120 _ => {}
121 }
122
123 Ok(())
124}