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 mut iter = value.as_bytes().iter().enumerate();
35
36        while let Some((pos, &ch)) = iter.next() {
37            match (ch, &state.token) {
38                (b'0'..=b'9', TokenType::Unknown | TokenType::Number) => {
39                    state.num = state
40                        .num
41                        .saturating_mul(10)
42                        .saturating_add((ch - b'0') as u64);
43                    state.token = TokenType::Number;
44                }
45                (b'*', TokenType::Unknown) => {
46                    state.token = TokenType::Wildcard;
47                }
48                (b'0', TokenType::Escaped) => {
49                    state.buf.push(b'~');
50                    state.token = TokenType::String;
51                }
52                (b'1', TokenType::Escaped) => {
53                    state.buf.push(b'/');
54                    state.token = TokenType::String;
55                }
56                (b'/', _) => {
57                    state.process();
58                    state.token = TokenType::Unknown;
59                    state.start_pos = pos + 1;
60                }
61                (_, _) => {
62                    if matches!(&state.token, TokenType::Number | TokenType::Wildcard)
63                        && pos > state.start_pos
64                    {
65                        state.buf.extend_from_slice(
66                            value
67                                .as_bytes()
68                                .get(state.start_pos..pos)
69                                .unwrap_or_default(),
70                        );
71                    }
72
73                    state.token = match ch {
74                        b'~' if !matches!(&state.token, TokenType::Escaped) => TokenType::Escaped,
75                        b'\\' => {
76                            state
77                                .buf
78                                .push(iter.next().map(|(_, &ch)| ch).unwrap_or(b'\\'));
79                            TokenType::String
80                        }
81                        _ => {
82                            state.buf.push(ch);
83                            TokenType::String
84                        }
85                    };
86                }
87            }
88        }
89
90        state.process();
91
92        if state.path.is_empty() {
93            state.path.push(JsonPointerItem::Root);
94        }
95
96        JsonPointer(state.path)
97    }
98}
99
100impl<P: Property> State<P> {
101    pub fn process(&mut self) {
102        match self.token {
103            TokenType::String => {
104                let item = std::str::from_utf8(&self.buf).unwrap_or_default();
105                match P::try_parse(self.path.last().and_then(|item| item.as_key()), item) {
106                    Some(prop) => {
107                        self.path.push(JsonPointerItem::Key(Key::Property(prop)));
108                    }
109                    None => {
110                        self.path
111                            .push(JsonPointerItem::Key(Key::Owned(item.to_string())));
112                    }
113                }
114
115                self.buf.clear();
116            }
117            TokenType::Number => {
118                self.path.push(JsonPointerItem::Number(self.num));
119                self.num = 0;
120            }
121            TokenType::Wildcard => {
122                self.path.push(JsonPointerItem::Wildcard);
123            }
124            TokenType::Unknown if self.start_pos > 0 => {
125                self.path.push(JsonPointerItem::Key("".into()));
126            }
127            _ => (),
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134
135    use crate::Null;
136
137    use super::{JsonPointer, JsonPointerItem};
138
139    #[test]
140    fn json_pointer_parse() {
141        for (input, output) in vec![
142            ("hello", vec![JsonPointerItem::<Null>::Key("hello".into())]),
143            ("9a", vec![JsonPointerItem::Key("9a".into())]),
144            ("a9", vec![JsonPointerItem::Key("a9".into())]),
145            ("*a", vec![JsonPointerItem::Key("*a".into())]),
146            (
147                "/hello/world",
148                vec![
149                    JsonPointerItem::Key("hello".into()),
150                    JsonPointerItem::Key("world".into()),
151                ],
152            ),
153            ("*", vec![JsonPointerItem::Wildcard]),
154            (
155                "/hello/*",
156                vec![
157                    JsonPointerItem::Key("hello".into()),
158                    JsonPointerItem::Wildcard,
159                ],
160            ),
161            ("1234", vec![JsonPointerItem::Number(1234)]),
162            (
163                "/hello/1234",
164                vec![
165                    JsonPointerItem::Key("hello".into()),
166                    JsonPointerItem::Number(1234),
167                ],
168            ),
169            ("~0~1", vec![JsonPointerItem::Key("~/".into())]),
170            (
171                "/hello/~0~1",
172                vec![
173                    JsonPointerItem::Key("hello".into()),
174                    JsonPointerItem::Key("~/".into()),
175                ],
176            ),
177            (
178                "/hello/1~0~1/*~1~0",
179                vec![
180                    JsonPointerItem::Key("hello".into()),
181                    JsonPointerItem::Key("1~/".into()),
182                    JsonPointerItem::Key("*/~".into()),
183                ],
184            ),
185            (
186                "/hello/world/*/99",
187                vec![
188                    JsonPointerItem::Key("hello".into()),
189                    JsonPointerItem::Key("world".into()),
190                    JsonPointerItem::Wildcard,
191                    JsonPointerItem::Number(99),
192                ],
193            ),
194            ("/", vec![JsonPointerItem::Key("".into())]),
195            (
196                "///",
197                vec![
198                    JsonPointerItem::Key("".into()),
199                    JsonPointerItem::Key("".into()),
200                    JsonPointerItem::Key("".into()),
201                ],
202            ),
203            ("", vec![JsonPointerItem::Root]),
204        ] {
205            assert_eq!(JsonPointer::parse(input).0, output, "{input}");
206        }
207    }
208}