1use alloc::boxed::Box;
13use alloc::string::{String, ToString};
14
15#[derive(Clone, Debug, PartialEq)]
17pub enum JsonPath {
18 Root,
20 Field(Box<JsonPath>, String),
22 Index(Box<JsonPath>, usize),
24 Slice(Box<JsonPath>, Option<usize>, Option<usize>),
26 RecursiveField(Box<JsonPath>, String),
28 Wildcard(Box<JsonPath>),
30 Filter(Box<JsonPath>, Box<JsonPathPredicate>),
32}
33
34#[derive(Clone, Debug, PartialEq)]
36pub enum JsonPathPredicate {
37 Compare(String, CompareOp, PredicateValue),
39 Exists(String),
41 And(Box<JsonPathPredicate>, Box<JsonPathPredicate>),
43 Or(Box<JsonPathPredicate>, Box<JsonPathPredicate>),
45 Not(Box<JsonPathPredicate>),
47}
48
49#[derive(Clone, Debug, PartialEq)]
51pub enum CompareOp {
52 Eq,
53 Ne,
54 Lt,
55 Le,
56 Gt,
57 Ge,
58}
59
60#[derive(Clone, Debug, PartialEq)]
62pub enum PredicateValue {
63 Null,
64 Bool(bool),
65 Number(f64),
66 String(String),
67}
68
69#[derive(Clone, Debug, PartialEq)]
71pub struct ParseError {
72 pub message: String,
73 pub position: usize,
74}
75
76impl ParseError {
77 fn new(message: impl Into<String>, position: usize) -> Self {
78 Self {
79 message: message.into(),
80 position,
81 }
82 }
83}
84
85struct Parser<'a> {
87 input: &'a str,
88 pos: usize,
89}
90
91impl<'a> Parser<'a> {
92 fn new(input: &'a str) -> Self {
93 Self { input, pos: 0 }
94 }
95
96 fn peek(&self) -> Option<char> {
97 self.input[self.pos..].chars().next()
98 }
99
100 fn advance(&mut self) {
101 if let Some(c) = self.peek() {
102 self.pos += c.len_utf8();
103 }
104 }
105
106 fn skip_whitespace(&mut self) {
107 while let Some(c) = self.peek() {
108 if c.is_whitespace() {
109 self.advance();
110 } else {
111 break;
112 }
113 }
114 }
115
116 fn expect(&mut self, expected: char) -> Result<(), ParseError> {
117 self.skip_whitespace();
118 match self.peek() {
119 Some(c) if c == expected => {
120 self.advance();
121 Ok(())
122 }
123 Some(c) => Err(ParseError::new(
124 alloc::format!("Expected '{}', found '{}'", expected, c),
125 self.pos,
126 )),
127 None => Err(ParseError::new(
128 alloc::format!("Expected '{}', found end of input", expected),
129 self.pos,
130 )),
131 }
132 }
133
134 fn parse_identifier(&mut self) -> Result<String, ParseError> {
135 self.skip_whitespace();
136 let start = self.pos;
137 while let Some(c) = self.peek() {
138 if c.is_alphanumeric() || c == '_' {
139 self.advance();
140 } else {
141 break;
142 }
143 }
144 if self.pos == start {
145 return Err(ParseError::new("Expected identifier", self.pos));
146 }
147 Ok(self.input[start..self.pos].to_string())
148 }
149
150 fn parse_number(&mut self) -> Result<f64, ParseError> {
151 self.skip_whitespace();
152 let start = self.pos;
153 if self.peek() == Some('-') {
154 self.advance();
155 }
156 while let Some(c) = self.peek() {
157 if c.is_ascii_digit() || c == '.' {
158 self.advance();
159 } else {
160 break;
161 }
162 }
163 if self.pos == start {
164 return Err(ParseError::new("Expected number", self.pos));
165 }
166 self.input[start..self.pos]
167 .parse()
168 .map_err(|_| ParseError::new("Invalid number", start))
169 }
170
171 fn parse_string_literal(&mut self) -> Result<String, ParseError> {
172 self.skip_whitespace();
173 let quote = self.peek();
174 if quote != Some('\'') && quote != Some('"') {
175 return Err(ParseError::new("Expected string literal", self.pos));
176 }
177 let quote = quote.unwrap();
178 self.advance();
179
180 let start = self.pos;
181 while let Some(c) = self.peek() {
182 if c == quote {
183 let result = self.input[start..self.pos].to_string();
184 self.advance();
185 return Ok(result);
186 }
187 if c == '\\' {
188 self.advance();
189 }
190 self.advance();
191 }
192 Err(ParseError::new("Unterminated string", start))
193 }
194}
195
196impl JsonPath {
197 pub fn parse(input: &str) -> Result<Self, ParseError> {
199 let mut parser = Parser::new(input);
200 parser.skip_whitespace();
201
202 parser.expect('$')?;
203 let mut path = JsonPath::Root;
204
205 loop {
206 parser.skip_whitespace();
207 match parser.peek() {
208 Some('.') => {
209 parser.advance();
210 if parser.peek() == Some('.') {
211 parser.advance();
212 let field = parser.parse_identifier()?;
213 path = JsonPath::RecursiveField(Box::new(path), field);
214 } else if parser.peek() == Some('*') {
215 parser.advance();
216 path = JsonPath::Wildcard(Box::new(path));
217 } else {
218 let field = parser.parse_identifier()?;
219 path = JsonPath::Field(Box::new(path), field);
220 }
221 }
222 Some('[') => {
223 parser.advance();
224 parser.skip_whitespace();
225
226 match parser.peek() {
227 Some('*') => {
228 parser.advance();
229 parser.expect(']')?;
230 path = JsonPath::Wildcard(Box::new(path));
231 }
232 Some('?') => {
233 parser.advance();
234 parser.expect('(')?;
235 let predicate = parse_predicate(&mut parser)?;
236 parser.expect(')')?;
237 parser.expect(']')?;
238 path = JsonPath::Filter(Box::new(path), Box::new(predicate));
239 }
240 Some('\'') | Some('"') => {
241 let field = parser.parse_string_literal()?;
242 parser.expect(']')?;
243 path = JsonPath::Field(Box::new(path), field);
244 }
245 Some(c) if c.is_ascii_digit() || c == ':' || c == '-' => {
246 let (start, end) = parse_index_or_slice(&mut parser)?;
247 parser.expect(']')?;
248 if start.is_some() && end.is_none() && !parser.input[..parser.pos].contains(':') {
249 path = JsonPath::Index(Box::new(path), start.unwrap());
250 } else {
251 path = JsonPath::Slice(Box::new(path), start, end);
252 }
253 }
254 _ => {
255 return Err(ParseError::new("Invalid bracket expression", parser.pos));
256 }
257 }
258 }
259 None => break,
260 _ => break,
261 }
262 }
263
264 Ok(path)
265 }
266}
267
268fn parse_index_or_slice(parser: &mut Parser) -> Result<(Option<usize>, Option<usize>), ParseError> {
269 parser.skip_whitespace();
270
271 let start = if parser.peek() == Some(':') {
272 None
273 } else if parser.peek().map(|c| c.is_ascii_digit()).unwrap_or(false) {
274 Some(parser.parse_number()? as usize)
275 } else {
276 None
277 };
278
279 parser.skip_whitespace();
280 if parser.peek() == Some(':') {
281 parser.advance();
282 parser.skip_whitespace();
283
284 let end = if parser.peek().map(|c| c.is_ascii_digit()).unwrap_or(false) {
285 Some(parser.parse_number()? as usize)
286 } else {
287 None
288 };
289
290 Ok((start, end))
291 } else {
292 Ok((start, None))
293 }
294}
295
296fn parse_predicate(parser: &mut Parser) -> Result<JsonPathPredicate, ParseError> {
297 parser.skip_whitespace();
298 parser.expect('@')?;
299 parser.expect('.')?;
300
301 let field = parser.parse_identifier()?;
302 parser.skip_whitespace();
303
304 match parser.peek() {
305 Some('=') => {
306 parser.advance();
307 let op = if parser.peek() == Some('=') {
308 parser.advance();
309 CompareOp::Eq
310 } else {
311 CompareOp::Eq
312 };
313 let value = parse_predicate_value(parser)?;
314 Ok(JsonPathPredicate::Compare(field, op, value))
315 }
316 Some('!') => {
317 parser.advance();
318 parser.expect('=')?;
319 let value = parse_predicate_value(parser)?;
320 Ok(JsonPathPredicate::Compare(field, CompareOp::Ne, value))
321 }
322 Some('<') => {
323 parser.advance();
324 let op = if parser.peek() == Some('=') {
325 parser.advance();
326 CompareOp::Le
327 } else {
328 CompareOp::Lt
329 };
330 let value = parse_predicate_value(parser)?;
331 Ok(JsonPathPredicate::Compare(field, op, value))
332 }
333 Some('>') => {
334 parser.advance();
335 let op = if parser.peek() == Some('=') {
336 parser.advance();
337 CompareOp::Ge
338 } else {
339 CompareOp::Gt
340 };
341 let value = parse_predicate_value(parser)?;
342 Ok(JsonPathPredicate::Compare(field, op, value))
343 }
344 _ => Ok(JsonPathPredicate::Exists(field)),
345 }
346}
347
348fn parse_predicate_value(parser: &mut Parser) -> Result<PredicateValue, ParseError> {
349 parser.skip_whitespace();
350
351 match parser.peek() {
352 Some('\'') | Some('"') => {
353 let s = parser.parse_string_literal()?;
354 Ok(PredicateValue::String(s))
355 }
356 Some('t') => {
357 let id = parser.parse_identifier()?;
358 if id == "true" {
359 Ok(PredicateValue::Bool(true))
360 } else {
361 Err(ParseError::new("Invalid value", parser.pos))
362 }
363 }
364 Some('f') => {
365 let id = parser.parse_identifier()?;
366 if id == "false" {
367 Ok(PredicateValue::Bool(false))
368 } else {
369 Err(ParseError::new("Invalid value", parser.pos))
370 }
371 }
372 Some('n') => {
373 let id = parser.parse_identifier()?;
374 if id == "null" {
375 Ok(PredicateValue::Null)
376 } else {
377 Err(ParseError::new("Invalid value", parser.pos))
378 }
379 }
380 Some(c) if c.is_ascii_digit() || c == '-' => {
381 let n = parser.parse_number()?;
382 Ok(PredicateValue::Number(n))
383 }
384 _ => Err(ParseError::new("Expected value", parser.pos)),
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn test_parse_root() {
394 let path = JsonPath::parse("$").unwrap();
395 assert_eq!(path, JsonPath::Root);
396 }
397
398 #[test]
399 fn test_parse_field() {
400 let path = JsonPath::parse("$.name").unwrap();
401 assert_eq!(
402 path,
403 JsonPath::Field(Box::new(JsonPath::Root), "name".into())
404 );
405 }
406
407 #[test]
408 fn test_parse_nested_fields() {
409 let path = JsonPath::parse("$.user.name").unwrap();
410 assert_eq!(
411 path,
412 JsonPath::Field(
413 Box::new(JsonPath::Field(Box::new(JsonPath::Root), "user".into())),
414 "name".into()
415 )
416 );
417 }
418
419 #[test]
420 fn test_parse_index() {
421 let path = JsonPath::parse("$[0]").unwrap();
422 assert_eq!(path, JsonPath::Index(Box::new(JsonPath::Root), 0));
423 }
424
425 #[test]
426 fn test_parse_field_and_index() {
427 let path = JsonPath::parse("$.items[0]").unwrap();
428 assert_eq!(
429 path,
430 JsonPath::Index(
431 Box::new(JsonPath::Field(Box::new(JsonPath::Root), "items".into())),
432 0
433 )
434 );
435 }
436
437 #[test]
438 fn test_parse_wildcard() {
439 let path = JsonPath::parse("$[*]").unwrap();
440 assert_eq!(path, JsonPath::Wildcard(Box::new(JsonPath::Root)));
441
442 let path = JsonPath::parse("$.*").unwrap();
443 assert_eq!(path, JsonPath::Wildcard(Box::new(JsonPath::Root)));
444 }
445
446 #[test]
447 fn test_parse_recursive() {
448 let path = JsonPath::parse("$..name").unwrap();
449 assert_eq!(
450 path,
451 JsonPath::RecursiveField(Box::new(JsonPath::Root), "name".into())
452 );
453 }
454
455 #[test]
456 fn test_parse_slice() {
457 let path = JsonPath::parse("$[0:10]").unwrap();
458 assert_eq!(
459 path,
460 JsonPath::Slice(Box::new(JsonPath::Root), Some(0), Some(10))
461 );
462
463 let path = JsonPath::parse("$[:5]").unwrap();
464 assert_eq!(
465 path,
466 JsonPath::Slice(Box::new(JsonPath::Root), None, Some(5))
467 );
468
469 let path = JsonPath::parse("$[5:]").unwrap();
470 assert_eq!(
471 path,
472 JsonPath::Slice(Box::new(JsonPath::Root), Some(5), None)
473 );
474 }
475
476 #[test]
477 fn test_parse_bracket_field() {
478 let path = JsonPath::parse("$['field-name']").unwrap();
479 assert_eq!(
480 path,
481 JsonPath::Field(Box::new(JsonPath::Root), "field-name".into())
482 );
483 }
484
485 #[test]
486 fn test_parse_filter() {
487 let path = JsonPath::parse("$[?(@.price < 10)]").unwrap();
488 assert_eq!(
489 path,
490 JsonPath::Filter(
491 Box::new(JsonPath::Root),
492 Box::new(JsonPathPredicate::Compare(
493 "price".into(),
494 CompareOp::Lt,
495 PredicateValue::Number(10.0)
496 ))
497 )
498 );
499 }
500
501 #[test]
502 fn test_parse_filter_string() {
503 let path = JsonPath::parse("$[?(@.name == 'Alice')]").unwrap();
504 assert_eq!(
505 path,
506 JsonPath::Filter(
507 Box::new(JsonPath::Root),
508 Box::new(JsonPathPredicate::Compare(
509 "name".into(),
510 CompareOp::Eq,
511 PredicateValue::String("Alice".into())
512 ))
513 )
514 );
515 }
516
517 #[test]
518 fn test_parse_complex() {
519 let path = JsonPath::parse("$.store.books[0].title").unwrap();
520 assert_eq!(
521 path,
522 JsonPath::Field(
523 Box::new(JsonPath::Index(
524 Box::new(JsonPath::Field(
525 Box::new(JsonPath::Field(Box::new(JsonPath::Root), "store".into())),
526 "books".into()
527 )),
528 0
529 )),
530 "title".into()
531 )
532 );
533 }
534
535 #[test]
537 fn test_parse_error_missing_root() {
538 let result = JsonPath::parse("name");
539 assert!(result.is_err());
540 }
541
542 #[test]
543 fn test_parse_error_invalid_bracket() {
544 let result = JsonPath::parse("$[abc]");
545 assert!(result.is_err());
546 }
547
548 #[test]
549 fn test_parse_error_unclosed_bracket() {
550 let result = JsonPath::parse("$[0");
551 assert!(result.is_err());
552 }
553
554 #[test]
555 fn test_parse_error_invalid_filter() {
556 let result = JsonPath::parse("$[?(@.price xyz)]");
557 assert!(result.is_err());
558 }
559
560 #[test]
561 fn test_parse_error_empty_string() {
562 let result = JsonPath::parse("");
563 assert!(result.is_err());
564 }
565
566 #[test]
567 fn test_parse_whitespace_handling() {
568 let path = JsonPath::parse(" $ . name ").unwrap();
569 assert_eq!(
570 path,
571 JsonPath::Field(Box::new(JsonPath::Root), "name".into())
572 );
573
574 let path = JsonPath::parse("$ [ 0 ]").unwrap();
575 assert_eq!(path, JsonPath::Index(Box::new(JsonPath::Root), 0));
576 }
577
578 #[test]
579 fn test_parse_filter_all_operators() {
580 let path = JsonPath::parse("$[?(@.x == 1)]").unwrap();
582 assert!(matches!(path, JsonPath::Filter(_, _)));
583
584 let path = JsonPath::parse("$[?(@.x != 1)]").unwrap();
586 assert!(matches!(path, JsonPath::Filter(_, _)));
587
588 let path = JsonPath::parse("$[?(@.x < 1)]").unwrap();
590 assert!(matches!(path, JsonPath::Filter(_, _)));
591
592 let path = JsonPath::parse("$[?(@.x <= 1)]").unwrap();
594 assert!(matches!(path, JsonPath::Filter(_, _)));
595
596 let path = JsonPath::parse("$[?(@.x > 1)]").unwrap();
598 assert!(matches!(path, JsonPath::Filter(_, _)));
599
600 let path = JsonPath::parse("$[?(@.x >= 1)]").unwrap();
602 assert!(matches!(path, JsonPath::Filter(_, _)));
603
604 let path = JsonPath::parse("$[?(@.x)]").unwrap();
606 assert!(matches!(path, JsonPath::Filter(_, _)));
607 }
608
609 #[test]
610 fn test_parse_filter_value_types() {
611 let path = JsonPath::parse("$[?(@.active == true)]").unwrap();
613 assert!(matches!(path, JsonPath::Filter(_, _)));
614
615 let path = JsonPath::parse("$[?(@.active == false)]").unwrap();
617 assert!(matches!(path, JsonPath::Filter(_, _)));
618
619 let path = JsonPath::parse("$[?(@.value == null)]").unwrap();
621 assert!(matches!(path, JsonPath::Filter(_, _)));
622
623 let path = JsonPath::parse("$[?(@.temp < -10)]").unwrap();
625 assert!(matches!(path, JsonPath::Filter(_, _)));
626
627 let path = JsonPath::parse("$[?(@.name == \"Alice\")]").unwrap();
629 assert!(matches!(path, JsonPath::Filter(_, _)));
630 }
631
632 #[test]
633 fn test_parse_double_quoted_bracket_field() {
634 let path = JsonPath::parse("$[\"field-name\"]").unwrap();
635 assert_eq!(
636 path,
637 JsonPath::Field(Box::new(JsonPath::Root), "field-name".into())
638 );
639 }
640
641 #[test]
642 fn test_parse_large_index() {
643 let path = JsonPath::parse("$[999999]").unwrap();
644 assert_eq!(path, JsonPath::Index(Box::new(JsonPath::Root), 999999));
645 }
646}