1use serde_json::Value;
2
3enum Step {
4 Key(String),
5 Index(usize),
6 Wildcard,
7}
8
9fn parse(path: &str) -> Option<Vec<Step>> {
10 let mut steps = Vec::new();
11 let trimmed = path.trim().trim_start_matches('$');
12 for raw in trimmed.split('.') {
13 if raw.is_empty() {
14 continue;
15 }
16 let mut rest = raw;
17 while let Some(lb) = rest.find('[') {
19 let key = &rest[..lb];
20 if !key.is_empty() {
21 steps.push(Step::Key(key.to_string()));
22 }
23 let rb_rel = rest[lb..].find(']')?;
24 let rb = lb + rb_rel;
25 let inner = &rest[lb + 1..rb];
26 if inner == "*" {
27 steps.push(Step::Wildcard);
28 } else {
29 steps.push(Step::Index(inner.parse().ok()?));
30 }
31 rest = &rest[rb + 1..];
32 }
33 if !rest.is_empty() {
34 steps.push(Step::Key(rest.to_string()));
35 }
36 }
37 Some(steps)
38}
39
40pub fn eval(value: &Value, path: &str) -> Vec<Value> {
43 let Some(steps) = parse(path) else {
44 return Vec::new();
45 };
46 let mut current: Vec<&Value> = vec![value];
47 for step in &steps {
48 let mut next: Vec<&Value> = Vec::new();
49 for v in ¤t {
50 match step {
51 Step::Key(k) => {
52 if let Some(child) = v.get(k) {
53 next.push(child);
54 }
55 }
56 Step::Index(i) => {
57 if let Some(child) = v.get(i) {
58 next.push(child);
59 }
60 }
61 Step::Wildcard => match v {
62 Value::Array(arr) => next.extend(arr.iter()),
63 Value::Object(map) => next.extend(map.values()),
64 _ => {}
65 },
66 }
67 }
68 current = next;
69 }
70 current.into_iter().cloned().collect()
71}
72
73#[cfg(test)]
74mod tests {
75 use super::eval;
76 use serde_json::json;
77
78 #[test]
79 fn nested_key() {
80 let v = json!({"a": {"b": 1}});
81 assert_eq!(eval(&v, "$.a.b"), vec![json!(1)]);
82 }
83
84 #[test]
85 fn array_index() {
86 let v = json!({"errors": [{"code": "E1"}, {"code": "E2"}]});
87 assert_eq!(eval(&v, "errors[0].code"), vec![json!("E1")]);
88 }
89
90 #[test]
91 fn array_wildcard() {
92 let v = json!({"errors": [{"code": "E1"}, {"code": "E2"}]});
93 assert_eq!(eval(&v, "$.errors[*].code"), vec![json!("E1"), json!("E2")]);
94 }
95
96 #[test]
97 fn missing_path_is_empty() {
98 let v = json!({"a": 1});
99 assert!(eval(&v, "$.nope.x").is_empty());
100 }
101
102 #[test]
103 fn invalid_index_is_empty() {
104 let v = json!({"a": [1]});
105 assert!(eval(&v, "a[x]").is_empty());
106 }
107}