oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1use super::{JsonPath, JsonPathToken, ParseError};
2use std::str::FromStr;
3
4impl<'a> JsonPath<'a> {
5 pub fn parse(path: &'a str) -> Result<Self, ParseError> {
6 let mut tokens = Vec::new();
7 let mut i = 0;
8 let mut iter = path.chars().peekable();
9 while let Some(c) = iter.next() {
10 i += c.len_utf8();
11 match c {
12 '$' => tokens.push(JsonPathToken::Root),
13 '@' => tokens.push(JsonPathToken::Current),
14 '.' => {
15 let c = iter.peek();
16 match c {
17 Some('.') => {
18 // '..' => recursive descent
19 tokens.push(JsonPathToken::RecursiveDescend);
20 iter.next();
21 i += 1;
22 }
23 Some('*') => {
24 // '.*' => wildcard operator
25 tokens.push(JsonPathToken::Wildcard);
26 iter.next();
27 i += 1;
28 }
29 Some(a) if a.is_alphabetic() => {
30 // '.{name}' => field name
31 let start = i;
32 while let Some(a) = iter.peek() {
33 if a.is_alphanumeric() || a == &'_' {
34 i += a.len_utf8();
35 iter.next();
36 } else {
37 break;
38 }
39 }
40 let end = i;
41 let member = &path[start..end];
42 tokens.push(JsonPathToken::Member(member));
43 }
44 Some(a) => {
45 return Err(invalid_char(*a, path));
46 }
47 None => {
48 return Err(ParseError::InvalidJsonPath(format!(
49 "Path cannot end with '.': `{}`",
50 path
51 )))
52 }
53 }
54 }
55 '[' => {
56 // get all values between '[..]' braces
57 let start = i;
58 let mut slice = &path[i..];
59 let mut quote_start = false;
60 let mut nesting = 1;
61 for c in iter.by_ref() {
62 i += c.len_utf8();
63 if c == '[' {
64 nesting += 1;
65 }
66 if c == ']' {
67 nesting -= 1;
68 if nesting == 0 && !quote_start {
69 slice = &slice[..(i - start - 1)];
70 break;
71 }
72 }
73 if c == '\'' {
74 quote_start = !quote_start;
75 }
76 }
77 if slice == "*" {
78 // '[*]' => wildcard operator
79 tokens.push(JsonPathToken::Wildcard);
80 } else if let Ok(index) = slice.parse::<i32>() {
81 // '[{number}]' => array index
82 tokens.push(JsonPathToken::Index(index));
83 } else if slice.starts_with('?') {
84 return Err(ParseError::InvalidJsonPath(format!(
85 "JSON Path predicate expressions are not supported yet: `{}`",
86 slice
87 )));
88 } else if slice.contains(':') {
89 // '[{?from}:{?to}:{?by}]' => slice operator
90 let mut split = slice.split(':');
91 let start = split
92 .next()
93 .and_then(|s| u32::from_str(s).ok())
94 .unwrap_or(0);
95 let end = split
96 .next()
97 .and_then(|s| u32::from_str(s).ok())
98 .unwrap_or(u32::MAX);
99 let by = split
100 .next()
101 .and_then(|s| u32::from_str(s).ok())
102 .unwrap_or(1);
103 tokens.push(JsonPathToken::Slice(start, end, by));
104 } else if slice.contains(',') {
105 // either member union ['a','b','c'] or index union [1, 2, 3]
106 let subslices = slice.split(',');
107 let mut i = subslices.into_iter();
108 if let Some(n) = i.next() {
109 let mut n = n.trim();
110 if n.starts_with('\'') && n.ends_with('\'') {
111 // member union
112 let mut members = Vec::new();
113 while {
114 members.push(&n[1..(n.len() - 1)]); // remove ' on both ends
115 match i.next() {
116 None => false,
117 Some(member) => {
118 n = member.trim();
119 if n.starts_with('\'') && n.ends_with('\'') {
120 true
121 } else {
122 return Err(ParseError::InvalidJsonPath(format!(
123 "substring `{}` is missing pairing `'` symbol",
124 n
125 )));
126 }
127 }
128 }
129 } {}
130 tokens.push(JsonPathToken::MemberUnion(members))
131 } else {
132 // index union
133 let mut indices = Vec::new();
134 while {
135 match n.parse::<i32>() {
136 Ok(i) => indices.push(i),
137 Err(err) => return Err(ParseError::InvalidJsonPath(format!("substring `{}` doesn't contain valid union of keys or indices: {}", slice, err))),
138 }
139 match i.next() {
140 None => false,
141 Some(index) => {
142 n = index.trim();
143 true
144 }
145 }
146 } {}
147 tokens.push(JsonPathToken::IndexUnion(indices))
148 }
149 }
150 } else if slice.starts_with('\'') && slice.ends_with('\'') {
151 // "['name with spaces']" => field name with spaces
152 let member = &slice[1..slice.len() - 1];
153 tokens.push(JsonPathToken::Member(member));
154 } else {
155 return Err(ParseError::InvalidJsonPath(format!(
156 "substring `{}` is not supported: `{}`",
157 slice, path
158 )));
159 }
160 }
161 '*' => tokens.push(JsonPathToken::Wildcard),
162 c if c.is_alphabetic() => {
163 // handle cases like `..name`, `$name` or `@name`
164 let start = i - c.len_utf8();
165 while let Some(a) = iter.peek() {
166 if a.is_alphanumeric() || a == &'_' {
167 i += a.len_utf8();
168 iter.next();
169 } else {
170 break;
171 }
172 }
173 let end = i;
174 let member = &path[start..end];
175 tokens.push(JsonPathToken::Member(member));
176 }
177 _ => {
178 return Err(invalid_char(c, path));
179 }
180 }
181 }
182 Ok(JsonPath { tokens })
183 }
184}
185
186fn invalid_char(c: char, path: &str) -> ParseError {
187 ParseError::InvalidJsonPath(format!("Invalid character `{}` in path: `{}`", c, path))
188}
189
190#[cfg(test)]
191mod test {
192 use crate::json_path::parse::{JsonPath, JsonPathToken};
193
194 #[test]
195 fn parse_root() {
196 let path = JsonPath::parse("$").unwrap();
197 assert_eq!(path.tokens, vec![JsonPathToken::Root]);
198 }
199
200 #[test]
201 fn parse_current() {
202 let path = JsonPath::parse("@").unwrap();
203 assert_eq!(path.tokens, vec![JsonPathToken::Current]);
204 }
205
206 #[test]
207 fn parse_wildcard() {
208 let path = JsonPath::parse("$.*").unwrap();
209 assert_eq!(
210 path.tokens,
211 vec![JsonPathToken::Root, JsonPathToken::Wildcard]
212 );
213 }
214
215 #[test]
216 fn parse_wildcard_quoted() {
217 let path = JsonPath::parse("$[*]").unwrap();
218 assert_eq!(
219 path.tokens,
220 vec![JsonPathToken::Root, JsonPathToken::Wildcard]
221 );
222 }
223
224 #[test]
225 fn parse_descend() {
226 let path = JsonPath::parse("$..").unwrap();
227 assert_eq!(
228 path.tokens,
229 vec![JsonPathToken::Root, JsonPathToken::RecursiveDescend]
230 );
231 }
232
233 #[test]
234 fn parse_descend_gradual() {
235 let path = JsonPath::parse("$..name").unwrap();
236 assert_eq!(
237 path.tokens,
238 vec![
239 JsonPathToken::Root,
240 JsonPathToken::RecursiveDescend,
241 JsonPathToken::Member("name")
242 ]
243 );
244 }
245
246 #[test]
247 fn parse_dot_member() {
248 let path = JsonPath::parse("$.key").unwrap();
249 assert_eq!(
250 path.tokens,
251 vec![JsonPathToken::Root, JsonPathToken::Member("key")]
252 );
253 }
254
255 #[test]
256 fn parse_quote_member() {
257 let path = JsonPath::parse("$['key']").unwrap();
258 assert_eq!(
259 path.tokens,
260 vec![JsonPathToken::Root, JsonPathToken::Member("key")]
261 );
262 }
263
264 #[test]
265 fn parse_index() {
266 let path = JsonPath::parse("$[0]").unwrap();
267 assert_eq!(
268 path.tokens,
269 vec![JsonPathToken::Root, JsonPathToken::Index(0)]
270 );
271 }
272
273 #[test]
274 fn parse_negative_index() {
275 let path = JsonPath::parse("$[-3]").unwrap();
276 assert_eq!(
277 path.tokens,
278 vec![JsonPathToken::Root, JsonPathToken::Index(-3)]
279 );
280 }
281
282 #[test]
283 fn parse_slice_bounded() {
284 let path = JsonPath::parse("$[1:3]").unwrap();
285 assert_eq!(
286 path.tokens,
287 vec![JsonPathToken::Root, JsonPathToken::Slice(1, 3, 1)]
288 );
289 }
290
291 #[test]
292 fn parse_slice_left_unbounded() {
293 let path = JsonPath::parse("$[:3]").unwrap();
294 assert_eq!(
295 path.tokens,
296 vec![JsonPathToken::Root, JsonPathToken::Slice(0, 3, 1)]
297 );
298 }
299
300 #[test]
301 fn parse_slice_right_unbounded() {
302 let path = JsonPath::parse("$[3:]").unwrap();
303 assert_eq!(
304 path.tokens,
305 vec![JsonPathToken::Root, JsonPathToken::Slice(3, u32::MAX, 1)]
306 );
307 }
308
309 #[test]
310 fn parse_slice_with_step() {
311 let path = JsonPath::parse("$[3::2]").unwrap();
312 assert_eq!(
313 path.tokens,
314 vec![JsonPathToken::Root, JsonPathToken::Slice(3, u32::MAX, 2)]
315 );
316 let path = JsonPath::parse("$[3:5:2]").unwrap();
317 assert_eq!(
318 path.tokens,
319 vec![JsonPathToken::Root, JsonPathToken::Slice(3, 5, 2)]
320 );
321 let path = JsonPath::parse("$[:3:2]").unwrap();
322 assert_eq!(
323 path.tokens,
324 vec![JsonPathToken::Root, JsonPathToken::Slice(0, 3, 2)]
325 );
326 }
327
328 #[test]
329 fn parse_member_union() {
330 let path = JsonPath::parse("$.users['a', 'bc', '']").unwrap();
331 assert_eq!(
332 path.tokens,
333 vec![
334 JsonPathToken::Root,
335 JsonPathToken::Member("users"),
336 JsonPathToken::MemberUnion(vec!["a", "bc", ""])
337 ]
338 );
339 }
340
341 #[test]
342 fn parse_index_union() {
343 let path = JsonPath::parse("$.users[0, 123, -1]").unwrap();
344 assert_eq!(
345 path.tokens,
346 vec![
347 JsonPathToken::Root,
348 JsonPathToken::Member("users"),
349 JsonPathToken::IndexUnion(vec![0, 123, -1])
350 ]
351 );
352 }
353
354 #[test]
355 fn parse_complex() {
356 let path = JsonPath::parse("$.key_1[0].key2[*]").unwrap();
357 assert_eq!(
358 path.tokens,
359 vec![
360 JsonPathToken::Root,
361 JsonPathToken::Member("key_1"),
362 JsonPathToken::Index(0),
363 JsonPathToken::Member("key2"),
364 JsonPathToken::Wildcard
365 ]
366 );
367 }
368
369 #[test]
370 fn parse_predicate_error() {
371 let res = JsonPath::parse("$.users[?(@['name'] == 'Alice')].surname");
372 assert!(res.is_err());
373 let err = res.unwrap_err();
374 assert!(err.to_string().contains("?(@['name'] == 'Alice')"));
375 }
376}
377