1use crate::Error;
2use serde::{Deserialize, Deserializer};
3use serde_json::Value as JsonValue;
4use serde_yaml::Value as YamlValue;
5use std::collections::VecDeque;
6
7#[derive(Debug)]
8struct CustomYamlValue {
9 value: YamlValue,
10 tagged_paths: Vec<String>,
11}
12
13impl<'de> Deserialize<'de> for CustomYamlValue {
14 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
15 where
16 D: Deserializer<'de>,
17 {
18 deserialize_custom_yaml(deserializer)
19 }
20}
21
22fn deserialize_custom_yaml<'de, D>(deserializer: D) -> Result<CustomYamlValue, D::Error>
23where
24 D: Deserializer<'de>,
25{
26 let mut value = YamlValue::deserialize(deserializer)?;
27 let mut path = VecDeque::new();
28 let mut tagged_paths = Vec::new();
29 collect_tagged_keys(&mut value, &mut path, &mut tagged_paths).map_err(|e| {
30 serde::de::Error::custom(format!("Error parsing YAML at path {:?}: {:?}", path, e))
31 })?;
32 Ok(CustomYamlValue {
33 value,
34 tagged_paths,
35 })
36}
37
38fn collect_tagged_keys(
39 node: &mut YamlValue,
40 path: &mut VecDeque<String>,
41 paths: &mut Vec<String>,
42) -> Result<(), Error> {
43 match node {
44 YamlValue::Mapping(map) => {
45 for (key, value) in map {
46 if let YamlValue::Tagged(tag) = &key {
47 let key_str = tag.as_ref().tag.to_string();
48 if key_str == "!sd" {
49 let new_val = tag
50 .as_ref()
51 .value
52 .as_str()
53 .ok_or(Error::YamlInvalidSDTag(key_str))?;
54 let full_path = build_full_path(path, new_val);
55 paths.push(full_path);
56 }
57 } else if let YamlValue::String(key) = &key {
58 path.push_back(key.to_string());
59 collect_tagged_keys(value, path, paths)?;
60 path.pop_back();
61 }
62 }
63 }
64 YamlValue::Sequence(seq) => {
65 for (index, value) in seq.iter_mut().enumerate() {
66 path.push_back(index.to_string());
67 collect_tagged_keys(value, path, paths)?;
68 if let YamlValue::Tagged(tag) = &value {
70 let key_str = tag.as_ref().tag.to_string();
71 if key_str == "!sd" {
72 let new_val = tag
73 .as_ref()
74 .value
75 .as_str()
76 .ok_or(Error::YamlInvalidSDTag(key_str))?;
77 *value = YamlValue::String(new_val.to_string());
78 }
79 }
80 path.pop_back();
81 }
82 }
83 YamlValue::Tagged(tag) => {
84 let key_str = tag.as_ref().tag.to_string();
85 if key_str == "!sd" {
86 let mut full_path = String::new();
87 for (index, path_fragment) in path.iter().enumerate() {
88 if index == 0 {
89 full_path = format!("/{}", path_fragment);
90 } else {
91 full_path = format!("{}/{}", full_path, path_fragment)
92 }
93 }
94 paths.push(full_path);
95 }
96 }
97 _ => {}
98 }
99
100 Ok(())
101}
102
103fn build_full_path(path: &VecDeque<String>, additional_segment: &str) -> String {
104 let full_path = path
105 .iter()
106 .fold(String::new(), |acc, frag| format!("{}/{}", acc, frag));
107 format!("{}/{}", full_path, additional_segment)
108}
109
110pub fn parse_yaml(yaml_str: &str) -> Result<(JsonValue, Vec<String>), Error> {
118 let yaml_value: CustomYamlValue = serde_yaml::from_str(yaml_str)?;
119 let json_str = serde_yaml::to_string(&yaml_value.value)?;
120 let json: JsonValue = serde_yaml::from_str(&json_str)?;
121 let tagged_paths = yaml_value.tagged_paths;
122 Ok((json, tagged_paths))
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_parse_yaml1() {
131 let yaml_str = r#"
132 sub: user_42
133 !sd given_name: John
134 !sd family_name: Doe
135 email: "johndoe@example.com"
136 phone_number: "+1-202-555-0101"
137 phone_number_verified: true
138 address:
139 !sd street_address: "123 Main St"
140 !sd locality: Anytown
141 region: Anystate
142 country: US
143 birthdate: "1940-01-01"
144 updated_at: 1570000000
145 !sd nationalities:
146 - US
147 - DE
148 "#;
149
150 let (json, tagged_paths) = parse_yaml(yaml_str).unwrap();
151 println!("{:?}", json);
152 println!("{:?}", tagged_paths);
153
154 assert_eq!(
155 json,
156 serde_json::json!({
157 "sub": "user_42",
158 "given_name": "John",
159 "family_name": "Doe",
160 "email": "johndoe@example.com",
161 "phone_number": "+1-202-555-0101",
162 "phone_number_verified": true,
163 "address": {
164 "street_address": "123 Main St",
165 "locality": "Anytown",
166 "region": "Anystate",
167 "country": "US"
168 },
169 "birthdate": "1940-01-01",
170 "updated_at": 1570000000,
171 "nationalities": [
172 "US",
173 "DE"
174 ]
175 })
176 );
177
178 assert_eq!(
179 tagged_paths,
180 vec![
181 "/given_name",
182 "/family_name",
183 "/address/street_address",
184 "/address/locality",
185 "/nationalities",
186 ]
187 );
188 }
189
190 #[test]
191 fn test_parse_yaml2() {
192 let yaml_str = r#"
193 sub: user_42
194 !sd given_name: John
195 !sd family_name: Doe
196 email: "johndoe@example.com"
197 phone_number: "+1-202-555-0101"
198 phone_number_verified: true
199 !sd address:
200 street_address: "123 Main St"
201 locality: Anytown
202 region: Anystate
203 country: US
204 birthdate: "1940-01-01"
205 updated_at: 1570000000
206 nationalities:
207 - !sd US
208 - !sd DE
209 - PL
210 "#;
211
212 let (json, tagged_paths) = parse_yaml(yaml_str).unwrap();
213 println!("{:?}", json);
214 println!("{:?}", tagged_paths);
215
216 assert_eq!(
217 json,
218 serde_json::json!({
219 "sub": "user_42",
220 "given_name": "John",
221 "family_name": "Doe",
222 "email": "johndoe@example.com",
223 "phone_number": "+1-202-555-0101",
224 "phone_number_verified": true,
225 "address": {
226 "street_address": "123 Main St",
227 "locality": "Anytown",
228 "region": "Anystate",
229 "country": "US"
230 },
231 "birthdate": "1940-01-01",
232 "updated_at": 1570000000,
233 "nationalities": [
234 "US",
235 "DE",
236 "PL"
237 ]
238 })
239 );
240
241 assert_eq!(
242 tagged_paths,
243 vec![
244 "/given_name",
245 "/family_name",
246 "/address",
247 "/nationalities/0",
248 "/nationalities/1",
249 ]
250 );
251 }
252}