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
132impl<'de, P: Property> serde::Deserialize<'de> for JsonPointer<P> {
133    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
134    where
135        D: serde::Deserializer<'de>,
136    {
137        <&str>::deserialize(deserializer).map(|s| JsonPointer::parse(s))
138    }
139}
140
141impl<P: Property> serde::Serialize for JsonPointer<P> {
142    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
143    where
144        S: serde::Serializer,
145    {
146        serializer.serialize_str(&self.to_string())
147    }
148}
149
150#[cfg(test)]
151mod tests {
152
153    use crate::Null;
154
155    use super::{JsonPointer, JsonPointerItem};
156
157    #[test]
158    fn json_pointer_parse() {
159        for (input, output) in vec![
160            ("hello", vec![JsonPointerItem::<Null>::Key("hello".into())]),
161            ("9a", vec![JsonPointerItem::Key("9a".into())]),
162            ("a9", vec![JsonPointerItem::Key("a9".into())]),
163            ("*a", vec![JsonPointerItem::Key("*a".into())]),
164            (
165                "/hello/world",
166                vec![
167                    JsonPointerItem::Key("hello".into()),
168                    JsonPointerItem::Key("world".into()),
169                ],
170            ),
171            ("*", vec![JsonPointerItem::Wildcard]),
172            (
173                "/hello/*",
174                vec![
175                    JsonPointerItem::Key("hello".into()),
176                    JsonPointerItem::Wildcard,
177                ],
178            ),
179            ("1234", vec![JsonPointerItem::Number(1234)]),
180            (
181                "/hello/1234",
182                vec![
183                    JsonPointerItem::Key("hello".into()),
184                    JsonPointerItem::Number(1234),
185                ],
186            ),
187            ("~0~1", vec![JsonPointerItem::Key("~/".into())]),
188            (
189                "/hello/~0~1",
190                vec![
191                    JsonPointerItem::Key("hello".into()),
192                    JsonPointerItem::Key("~/".into()),
193                ],
194            ),
195            (
196                "/hello/1~0~1/*~1~0",
197                vec![
198                    JsonPointerItem::Key("hello".into()),
199                    JsonPointerItem::Key("1~/".into()),
200                    JsonPointerItem::Key("*/~".into()),
201                ],
202            ),
203            (
204                "/hello/world/*/99",
205                vec![
206                    JsonPointerItem::Key("hello".into()),
207                    JsonPointerItem::Key("world".into()),
208                    JsonPointerItem::Wildcard,
209                    JsonPointerItem::Number(99),
210                ],
211            ),
212            ("/", vec![JsonPointerItem::Key("".into())]),
213            (
214                "///",
215                vec![
216                    JsonPointerItem::Key("".into()),
217                    JsonPointerItem::Key("".into()),
218                    JsonPointerItem::Key("".into()),
219                ],
220            ),
221            ("", vec![JsonPointerItem::Root]),
222        ] {
223            assert_eq!(JsonPointer::parse(input).0, output, "{input}");
224        }
225    }
226}