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