dev_tool/conf_util_yaml.rs
1
2use crate::FileUtil;
3use serde_yaml::Value;
4use std::path::Path;
5
6/// Yaml 数据读取
7/// ```rust
8/// use dev_tool::YamlWrapper;
9///
10/// #[test]
11/// fn test_config_util() {
12/// // 读取配置文件
13/// let wrapper = YamlWrapper::new("docs/config.yaml").unwrap();
14/// // 直接将yaml字符串转换成YamlWrapper
15/// // let warpper = YamlWrapper::from_string("......").unwrap();
16///
17/// // 不管是对象,还是数组,都是直接通过`.`操作。address是对象,children是数组,name是children中对象的一个属性
18/// let x = wrapper.get("address.children.name");
19/// println!("address.children.name = {:?}", x);
20///
21/// // get方法是获取数组,而get_one获取的是第一个元素
22/// let x = wrapper.get_one("address.x.y").as_str().unwrap();
23/// println!("address.x.y = {}", x);
24/// }
25/// ```
26#[derive(Debug)]
27pub struct YamlWrapper {
28 pub raw_data: String,
29 pub data: Value,
30}
31
32pub struct ValueWrapper<'a> {
33 value: &'a Value,
34}
35
36impl<'a> ValueWrapper<'a> {
37 /// 创建一个新的实例
38 ///
39 /// # 参数
40 /// * `value` - 指向Value的引用
41 ///
42 /// # 返回值
43 /// 返回包含指定value的新实例
44 pub fn new(value: &'a Value) -> Self {
45 Self { value }
46 }
47
48 /// 根据键名获取对应的值
49 ///
50 /// 该函数支持通过点号分隔的多层键名来访问嵌套的数据结构,
51 /// 也支持在数组中查找指定键名的值。
52 ///
53 /// # 参数
54 /// * `key` - 要查找的键名,可以是单层键名或点号分隔的多层键名
55 ///
56 /// # 返回值
57 /// 返回包含查找到的值的向量,如果未找到则包含Null值
58 pub fn get(&self, key: &'a str) -> Vec<&'a Value> {
59 let mut results = Vec::new();
60 if let Some((left, right)) = key.split_once(".") {
61 // 处理多层键名的情况
62 let x = self.value.get(left);
63 if x.is_none() {
64 results.push(&Value::Null);
65 } else {
66 let x = x.unwrap();
67 let next = ValueWrapper::new(x);
68 results.append(&mut next.get(right));
69 }
70 } else {
71 // 处理单层键名的情况
72 match self.value {
73 Value::Mapping(map) => {
74 // 在对象中查找键名
75 if let Some(v) = map.get(key) {
76 results.push(v);
77 } else {
78 results.push(&Value::Null);
79 }
80 }
81 Value::Sequence(vec) => {
82 // 在数组的每个元素中查找键名
83 for item in vec {
84 match item.get(key) {
85 Some(v) => results.push(v),
86 None => results.push(&Value::Null),
87 }
88 }
89 }
90 _ => results.push(&Value::Null),
91 }
92 }
93 results
94 }
95}
96
97impl YamlWrapper {
98 /// 创建一个新的配置实例
99 ///
100 /// 该函数会读取指定路径的配置文件,解析JSON格式的内容,并创建配置对象
101 ///
102 /// # 参数
103 /// * `file_path` - 配置文件的路径字符串引用
104 ///
105 /// # 返回值
106 /// 返回Result类型,成功时包含配置实例,失败时包含IO错误
107 ///
108 /// # 错误
109 /// 当文件读取失败或JSON解析失败时会返回相应的错误
110 pub fn new(file_path: &str) -> Result<Self, Box<dyn std::error::Error>> {
111 // 读取配置文件内容
112 let conf_string = FileUtil::read_string(Path::new(file_path))?;
113 // 解析JSON数据
114 let data: Value = serde_yaml::from_str(&conf_string)?;
115 Ok(Self {
116 raw_data: conf_string,
117 data,
118 })
119 }
120
121 pub fn from_string(conf_string: &str) -> Result<Self, Box<dyn std::error::Error>> {
122 let data: Value = serde_yaml::from_str(conf_string)?;
123 Ok(Self {
124 raw_data: conf_string.to_string(),
125 data,
126 })
127 }
128
129 /// 获取指定键对应的值列表
130 ///
131 /// 该函数通过创建一个值包装器来访问内部数据,并返回与给定键关联的所有值。
132 ///
133 /// # 参数
134 /// * `key` - 要查找的静态字符串键
135 ///
136 /// # 返回值
137 /// 返回一个包含所有匹配值的引用向量
138 pub fn get<'a, 'b: 'a>(&'a self, key: &'b str) -> Vec<&'a Value> {
139 let wrapper = ValueWrapper::new(&self.data);
140 wrapper.get(key)
141 }
142
143 /// 获取指定键对应的第一个值
144 ///
145 /// # 参数
146 /// * `key` - 要查找的键名,必须是静态字符串
147 ///
148 /// # 返回值
149 /// 返回键对应值的引用
150 ///
151 /// # 注意
152 /// 如果键不存在或对应的值为空,会触发panic
153 pub fn get_one<'a, 'b: 'a>(&'a self, key: &'b str) -> &'a Value {
154 // 该方法注释,要求b的生命周期不小于a的生命周期
155 // 创建值包装器来处理数据查询
156 let wrapper = ValueWrapper::new(&self.data);
157 // 获取指定键对应的值向量
158 let vec = wrapper.get(key);
159 // 返回向量中的第一个值,如果不存在则panic
160 vec.first().unwrap()
161 }
162}
163
164#[cfg(test)]
165mod tests {
166
167 use super::*;
168
169 #[test]
170 fn test_json_conf_util() {
171
172 let wrapper = YamlWrapper::new("docs/config.yaml").unwrap();
173
174 let x = wrapper.get("name");
175 // name = [String("Alice")]
176 println!("name = {:?}", x);
177
178 let x = wrapper.get("age");
179 // age = [Number(30)]
180 println!("age = {:?}", x);
181
182 let x = wrapper.get("is_student");
183 // is_student = [Bool(false)]
184 println!("is_student = {:?}", x);
185
186 let x = wrapper.get("hobbies");
187 // hobbies = [Sequence [String("Reading"), String("Hiking"), String("Cooking")]]
188 println!("hobbies = {:?}", x);
189
190 let x = wrapper.get("address");
191 // address = [Mapping {"street": String("123 Main St"), "city": String("Anytown"), "state": String("CA"), "zip": String("10001"), "releases": Sequence [String("v1"), String("v2")], "x": Mapping {"y": String("hello, json!")}, "children": Sequence [Mapping {"name": String("r"), "age": Number(5)}, Mapping {"name": String("s"), "age": Number(6)}]}]
192 println!("address = {:?}", x);
193
194 let x = wrapper.get("address.street");
195 // address.street = [String("123 Main St")]
196 println!("address.street = {:?}", x);
197
198 let x = wrapper.get("address.releases");
199 // address.releases = [Sequence [String("v1"), String("v2")]]
200 println!("address.releases = {:?}", x);
201
202 let x = wrapper.get("address.x.y");
203 // address.x.y = [String("hello, json!")]
204 println!("address.x.y = {:?}", x);
205
206 let x = wrapper.get("address.children.name");
207 // address.children.name = [String("r"), String("s")]
208 println!("address.children.name = {:?}", x);
209 println!("=============================================================");
210
211 let name = wrapper.get_one("name").as_str().unwrap();
212 let age = wrapper.get_one("age").as_i64().unwrap();
213 println!("name = {:?}", name);
214 println!("age = {:?}", age);
215
216 let key = String::from("address.x.y");
217 let x = wrapper.get_one(&key).as_str().unwrap();
218 // address.x.y = hello, json!
219 println!("address.x.y = {}", x);
220
221 }
222}