sdjwt/
parser.rs

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                // Ugly hack to remove tag from sequence
69                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
110/// Parses claims as a YAML string, converts it to JSON, and collects paths of elements tagged with `!sd`.
111///
112/// # Arguments
113/// * `yaml_str` - A string slice that holds the YAML data.
114///
115/// # Returns
116/// A `Result` containing a tuple of JSON value and a vector of tagged paths, or an error.
117pub 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}