Skip to main content

jmap_tools/pointer/
parser.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use crate::{JsonPointer, JsonPointerItem, Key, Property};
8use std::borrow::Cow;
9
10enum TokenType {
11    Unknown,
12    Number,
13    String,
14    Wildcard,
15    Escaped,
16}
17
18struct State<P: Property> {
19    num: u64,
20    buf: Vec<u8>,
21    token: TokenType,
22    start_pos: usize,
23    path: Vec<JsonPointerItem<P>>,
24}
25
26impl<P: Property> JsonPointer<P> {
27    pub fn parse(value: &str) -> Self {
28        let mut state = State {
29            num: 0,
30            buf: Vec::new(),
31            token: TokenType::Unknown,
32            start_pos: 0,
33            path: Vec::new(),
34        };
35        let value = value.as_bytes();
36        let mut iter = value.iter().enumerate();
37
38        while let Some((pos, &ch)) = iter.next() {
39            match (ch, &state.token) {
40                (b'0'..=b'9', TokenType::Unknown | TokenType::Number) => {
41                    state.num = state
42                        .num
43                        .saturating_mul(10)
44                        .saturating_add((ch - b'0') as u64);
45                    state.token = TokenType::Number;
46                }
47                (b'*', TokenType::Unknown) => {
48                    state.token = TokenType::Wildcard;
49                }
50                (b'0', TokenType::Escaped) => {
51                    state.buf.push(b'~');
52                    state.token = TokenType::String;
53                }
54                (b'1', TokenType::Escaped) => {
55                    state.buf.push(b'/');
56                    state.token = TokenType::String;
57                }
58                (b'/', _) => {
59                    state.process(&value[state.start_pos..pos]);
60                    state.token = TokenType::Unknown;
61                    state.start_pos = pos + 1;
62                }
63                (_, _) => {
64                    if matches!(&state.token, TokenType::Number | TokenType::Wildcard)
65                        && pos > state.start_pos
66                    {
67                        state
68                            .buf
69                            .extend_from_slice(value.get(state.start_pos..pos).unwrap_or_default());
70                    }
71
72                    state.token = match ch {
73                        b'~' if !matches!(&state.token, TokenType::Escaped) => TokenType::Escaped,
74                        b'\\' => {
75                            state
76                                .buf
77                                .push(iter.next().map(|(_, &ch)| ch).unwrap_or(b'\\'));
78                            TokenType::String
79                        }
80                        _ => {
81                            state.buf.push(ch);
82                            TokenType::String
83                        }
84                    };
85                }
86            }
87        }
88
89        state.process(value.get(state.start_pos..).unwrap_or_default());
90
91        if state.path.is_empty() {
92            state.path.push(JsonPointerItem::Root);
93        }
94
95        JsonPointer(state.path)
96    }
97}
98
99impl<P: Property> State<P> {
100    pub fn process(&mut self, token_bytes: &[u8]) {
101        match self.token {
102            TokenType::String => {
103                let item = std::str::from_utf8(&self.buf).unwrap_or_default();
104                match P::try_parse(self.path.last().and_then(|item| item.as_key()), item) {
105                    Some(prop) => {
106                        self.path.push(JsonPointerItem::Key(Key::Property(prop)));
107                    }
108                    None => {
109                        self.path
110                            .push(JsonPointerItem::Key(Key::Owned(item.to_string())));
111                    }
112                }
113
114                self.buf.clear();
115            }
116            TokenType::Number => {
117                let item = std::str::from_utf8(token_bytes).unwrap_or_default();
118                match P::try_parse(self.path.last().and_then(|item| item.as_key()), item) {
119                    Some(prop) => {
120                        self.path.push(JsonPointerItem::Key(Key::Property(prop)));
121                    }
122                    None if token_bytes.first() == Some(&b'0') && token_bytes.len() > 1 => {
123                        self.path
124                            .push(JsonPointerItem::Key(Key::Owned(item.to_string())));
125                    }
126                    None => {
127                        self.path.push(JsonPointerItem::Number(self.num));
128                    }
129                }
130                self.num = 0;
131            }
132            TokenType::Wildcard => {
133                self.path.push(JsonPointerItem::Wildcard);
134            }
135            TokenType::Unknown if self.start_pos > 0 => {
136                self.path.push(JsonPointerItem::Key("".into()));
137            }
138            _ => (),
139        }
140    }
141}
142
143impl<'de, P: Property> serde::Deserialize<'de> for JsonPointer<P> {
144    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
145    where
146        D: serde::Deserializer<'de>,
147    {
148        <Cow<'de, str>>::deserialize(deserializer).map(|s| JsonPointer::parse(s.as_ref()))
149    }
150}
151
152impl<P: Property> serde::Serialize for JsonPointer<P> {
153    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154    where
155        S: serde::Serializer,
156    {
157        serializer.serialize_str(&self.to_string())
158    }
159}
160
161#[cfg(test)]
162mod tests {
163
164    use super::{JsonPointer, JsonPointerItem};
165    use crate::{Key, Null, Property};
166    use std::borrow::Cow;
167
168    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
169    enum TestProp {
170        Ids,
171        Id(String),
172    }
173
174    impl Property for TestProp {
175        fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
176            if let Some(Key::Property(TestProp::Ids)) = key {
177                Some(TestProp::Id(value.to_string()))
178            } else if value == "ids" {
179                Some(TestProp::Ids)
180            } else {
181                None
182            }
183        }
184
185        fn to_cow(&self) -> Cow<'static, str> {
186            match self {
187                TestProp::Ids => Cow::Borrowed("ids"),
188                TestProp::Id(s) => Cow::Owned(s.clone()),
189            }
190        }
191    }
192
193    #[test]
194    fn json_pointer_parse() {
195        for (input, output) in vec![
196            ("hello", vec![JsonPointerItem::<Null>::Key("hello".into())]),
197            ("9a", vec![JsonPointerItem::Key("9a".into())]),
198            ("a9", vec![JsonPointerItem::Key("a9".into())]),
199            ("*a", vec![JsonPointerItem::Key("*a".into())]),
200            (
201                "/hello/world",
202                vec![
203                    JsonPointerItem::Key("hello".into()),
204                    JsonPointerItem::Key("world".into()),
205                ],
206            ),
207            ("*", vec![JsonPointerItem::Wildcard]),
208            (
209                "/hello/*",
210                vec![
211                    JsonPointerItem::Key("hello".into()),
212                    JsonPointerItem::Wildcard,
213                ],
214            ),
215            ("1234", vec![JsonPointerItem::Number(1234)]),
216            (
217                "/hello/1234",
218                vec![
219                    JsonPointerItem::Key("hello".into()),
220                    JsonPointerItem::Number(1234),
221                ],
222            ),
223            (
224                "/hello/01",
225                vec![
226                    JsonPointerItem::Key("hello".into()),
227                    JsonPointerItem::Key("01".into()),
228                ],
229            ),
230            ("~0~1", vec![JsonPointerItem::Key("~/".into())]),
231            (
232                "/hello/~0~1",
233                vec![
234                    JsonPointerItem::Key("hello".into()),
235                    JsonPointerItem::Key("~/".into()),
236                ],
237            ),
238            (
239                "/hello/1~0~1/*~1~0",
240                vec![
241                    JsonPointerItem::Key("hello".into()),
242                    JsonPointerItem::Key("1~/".into()),
243                    JsonPointerItem::Key("*/~".into()),
244                ],
245            ),
246            (
247                "/hello/world/*/99",
248                vec![
249                    JsonPointerItem::Key("hello".into()),
250                    JsonPointerItem::Key("world".into()),
251                    JsonPointerItem::Wildcard,
252                    JsonPointerItem::Number(99),
253                ],
254            ),
255            ("/", vec![JsonPointerItem::Key("".into())]),
256            (
257                "///",
258                vec![
259                    JsonPointerItem::Key("".into()),
260                    JsonPointerItem::Key("".into()),
261                    JsonPointerItem::Key("".into()),
262                ],
263            ),
264            ("", vec![JsonPointerItem::Root]),
265        ] {
266            assert_eq!(JsonPointer::parse(input).0, output, "{input}");
267        }
268    }
269
270    #[test]
271    fn json_pointer_parse_promotes_digit() {
272        let pointer = JsonPointer::<TestProp>::parse("ids/2");
273        assert_eq!(
274            pointer.0,
275            vec![
276                JsonPointerItem::Key(Key::Property(TestProp::Ids)),
277                JsonPointerItem::Key(Key::Property(TestProp::Id("2".to_string()))),
278            ]
279        );
280
281        let pointer = JsonPointer::<TestProp>::parse("ids/abc");
282        assert_eq!(
283            pointer.0,
284            vec![
285                JsonPointerItem::Key(Key::Property(TestProp::Ids)),
286                JsonPointerItem::Key(Key::Property(TestProp::Id("abc".to_string()))),
287            ]
288        );
289
290        let pointer = JsonPointer::<TestProp>::parse("other/2");
291        assert_eq!(
292            pointer.0,
293            vec![
294                JsonPointerItem::Key(Key::Owned("other".to_string())),
295                JsonPointerItem::Number(2),
296            ]
297        );
298    }
299
300    #[test]
301    fn json_pointer_parse_leading_zero_is_string() {
302        let pointer = JsonPointer::<TestProp>::parse("other/07");
303        assert_eq!(
304            pointer.0,
305            vec![
306                JsonPointerItem::Key(Key::Owned("other".to_string())),
307                JsonPointerItem::Key(Key::Owned("07".to_string())),
308            ]
309        );
310
311        let pointer = JsonPointer::<TestProp>::parse("other/00");
312        assert_eq!(
313            pointer.0,
314            vec![
315                JsonPointerItem::Key(Key::Owned("other".to_string())),
316                JsonPointerItem::Key(Key::Owned("00".to_string())),
317            ]
318        );
319
320        let pointer = JsonPointer::<TestProp>::parse("other/0");
321        assert_eq!(
322            pointer.0,
323            vec![
324                JsonPointerItem::Key(Key::Owned("other".to_string())),
325                JsonPointerItem::Number(0),
326            ]
327        );
328
329        let pointer = JsonPointer::<TestProp>::parse("other/70");
330        assert_eq!(
331            pointer.0,
332            vec![
333                JsonPointerItem::Key(Key::Owned("other".to_string())),
334                JsonPointerItem::Number(70),
335            ]
336        );
337    }
338
339    #[test]
340    fn deserializes_pointers_with_escaped_solidus() {
341        let plain: JsonPointer<Null> = serde_json::from_str(r#""/list/*/id""#).expect("plain");
342        let escaped: JsonPointer<Null> =
343            serde_json::from_str(r#""\/list\/*\/id""#).expect("escaped solidus must deserialize");
344
345        assert_eq!(plain, escaped);
346    }
347}