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