Skip to main content

cynos_jsonb/path/
eval.rs

1//! JSONPath evaluation for JSONB values.
2//!
3//! This module provides the evaluation logic for JSONPath expressions
4//! against JsonbValue instances.
5
6use crate::path::parser::{CompareOp, JsonPath, JsonPathPredicate, PredicateValue};
7use crate::value::JsonbValue;
8use alloc::vec::Vec;
9
10impl JsonbValue {
11    /// Evaluates a JSONPath expression and returns all matching values.
12    pub fn query<'a>(&'a self, path: &JsonPath) -> Vec<&'a JsonbValue> {
13        let mut results = Vec::new();
14        eval_path(self, path, &mut results);
15        results
16    }
17
18    /// Evaluates a JSONPath expression and returns the first matching value.
19    pub fn query_first<'a>(&'a self, path: &JsonPath) -> Option<&'a JsonbValue> {
20        self.query(path).into_iter().next()
21    }
22}
23
24fn eval_path<'a>(value: &'a JsonbValue, path: &JsonPath, results: &mut Vec<&'a JsonbValue>) {
25    match path {
26        JsonPath::Root => {
27            results.push(value);
28        }
29        JsonPath::Field(parent, field) => {
30            let parent_results = eval_path_collect(value, parent);
31            for v in parent_results {
32                if let Some(obj) = v.as_object() {
33                    if let Some(field_value) = obj.get(field) {
34                        results.push(field_value);
35                    }
36                }
37            }
38        }
39        JsonPath::Index(parent, index) => {
40            let parent_results = eval_path_collect(value, parent);
41            for v in parent_results {
42                if let Some(arr) = v.as_array() {
43                    if let Some(item) = arr.get(*index) {
44                        results.push(item);
45                    }
46                }
47            }
48        }
49        JsonPath::Slice(parent, start, end) => {
50            let parent_results = eval_path_collect(value, parent);
51            for v in parent_results {
52                if let Some(arr) = v.as_array() {
53                    let start_idx = start.unwrap_or(0);
54                    let end_idx = end.unwrap_or(arr.len());
55                    for item in arr.iter().skip(start_idx).take(end_idx - start_idx) {
56                        results.push(item);
57                    }
58                }
59            }
60        }
61        JsonPath::Wildcard(parent) => {
62            let parent_results = eval_path_collect(value, parent);
63            for v in parent_results {
64                match v {
65                    JsonbValue::Array(arr) => {
66                        for item in arr {
67                            results.push(item);
68                        }
69                    }
70                    JsonbValue::Object(obj) => {
71                        for (_, val) in obj.iter() {
72                            results.push(val);
73                        }
74                    }
75                    _ => {}
76                }
77            }
78        }
79        JsonPath::RecursiveField(parent, field) => {
80            let parent_results = eval_path_collect(value, parent);
81            for v in parent_results {
82                recursive_field_search(v, field, results);
83            }
84        }
85        JsonPath::Filter(parent, predicate) => {
86            let parent_results = eval_path_collect(value, parent);
87            for v in parent_results {
88                if let Some(arr) = v.as_array() {
89                    for item in arr {
90                        if eval_predicate(item, predicate) {
91                            results.push(item);
92                        }
93                    }
94                }
95            }
96        }
97    }
98}
99
100fn eval_path_collect<'a>(value: &'a JsonbValue, path: &JsonPath) -> Vec<&'a JsonbValue> {
101    let mut results = Vec::new();
102    eval_path(value, path, &mut results);
103    results
104}
105
106fn recursive_field_search<'a>(
107    value: &'a JsonbValue,
108    field: &str,
109    results: &mut Vec<&'a JsonbValue>,
110) {
111    match value {
112        JsonbValue::Object(obj) => {
113            if let Some(v) = obj.get(field) {
114                results.push(v);
115            }
116            for (_, v) in obj.iter() {
117                recursive_field_search(v, field, results);
118            }
119        }
120        JsonbValue::Array(arr) => {
121            for item in arr {
122                recursive_field_search(item, field, results);
123            }
124        }
125        _ => {}
126    }
127}
128
129fn eval_predicate(value: &JsonbValue, predicate: &JsonPathPredicate) -> bool {
130    match predicate {
131        JsonPathPredicate::Exists(field) => {
132            if let Some(obj) = value.as_object() {
133                obj.contains_key(field)
134            } else {
135                false
136            }
137        }
138        JsonPathPredicate::Compare(field, op, expected) => {
139            if let Some(obj) = value.as_object() {
140                if let Some(actual) = obj.get(field) {
141                    compare_values(actual, op, expected)
142                } else {
143                    false
144                }
145            } else {
146                false
147            }
148        }
149        JsonPathPredicate::And(left, right) => {
150            eval_predicate(value, left) && eval_predicate(value, right)
151        }
152        JsonPathPredicate::Or(left, right) => {
153            eval_predicate(value, left) || eval_predicate(value, right)
154        }
155        JsonPathPredicate::Not(inner) => !eval_predicate(value, inner),
156    }
157}
158
159fn compare_values(actual: &JsonbValue, op: &CompareOp, expected: &PredicateValue) -> bool {
160    match (actual, expected) {
161        (JsonbValue::Null, PredicateValue::Null) => matches!(op, CompareOp::Eq),
162        (JsonbValue::Bool(a), PredicateValue::Bool(b)) => match op {
163            CompareOp::Eq => a == b,
164            CompareOp::Ne => a != b,
165            _ => false,
166        },
167        (JsonbValue::Number(a), PredicateValue::Number(b)) => match op {
168            CompareOp::Eq => (a - b).abs() < f64::EPSILON,
169            CompareOp::Ne => (a - b).abs() >= f64::EPSILON,
170            CompareOp::Lt => a < b,
171            CompareOp::Le => a <= b,
172            CompareOp::Gt => a > b,
173            CompareOp::Ge => a >= b,
174        },
175        (JsonbValue::String(a), PredicateValue::String(b)) => match op {
176            CompareOp::Eq => a == b,
177            CompareOp::Ne => a != b,
178            CompareOp::Lt => a < b,
179            CompareOp::Le => a <= b,
180            CompareOp::Gt => a > b,
181            CompareOp::Ge => a >= b,
182        },
183        _ => false,
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::value::JsonbObject;
191    use alloc::vec;
192
193    fn make_test_json() -> JsonbValue {
194        let mut user = JsonbObject::new();
195        user.insert("name".into(), JsonbValue::String("Alice".into()));
196        user.insert(
197            "tags".into(),
198            JsonbValue::Array(vec![
199                JsonbValue::String("admin".into()),
200                JsonbValue::String("developer".into()),
201            ]),
202        );
203
204        let mut root = JsonbObject::new();
205        root.insert("user".into(), JsonbValue::Object(user));
206        JsonbValue::Object(root)
207    }
208
209    #[test]
210    fn test_query_root() {
211        let json = make_test_json();
212        let path = JsonPath::parse("$").unwrap();
213        let results = json.query(&path);
214        assert_eq!(results.len(), 1);
215    }
216
217    #[test]
218    fn test_query_field() {
219        let json = make_test_json();
220        let path = JsonPath::parse("$.user.name").unwrap();
221        let results = json.query(&path);
222        assert_eq!(results, vec![&JsonbValue::String("Alice".into())]);
223    }
224
225    #[test]
226    fn test_query_array_index() {
227        let json = make_test_json();
228        let path = JsonPath::parse("$.user.tags[0]").unwrap();
229        let results = json.query(&path);
230        assert_eq!(results, vec![&JsonbValue::String("admin".into())]);
231    }
232
233    #[test]
234    fn test_query_array_slice() {
235        let json = make_test_json();
236        let path = JsonPath::parse("$.user.tags[0:2]").unwrap();
237        let results = json.query(&path);
238        assert_eq!(results.len(), 2);
239        assert_eq!(results[0], &JsonbValue::String("admin".into()));
240        assert_eq!(results[1], &JsonbValue::String("developer".into()));
241    }
242
243    #[test]
244    fn test_query_wildcard() {
245        let json = make_test_json();
246        let path = JsonPath::parse("$.user.tags[*]").unwrap();
247        let results = json.query(&path);
248        assert_eq!(results.len(), 2);
249    }
250
251    #[test]
252    fn test_query_recursive() {
253        let mut inner = JsonbObject::new();
254        inner.insert("name".into(), JsonbValue::String("inner".into()));
255
256        let mut outer = JsonbObject::new();
257        outer.insert("name".into(), JsonbValue::String("outer".into()));
258        outer.insert("child".into(), JsonbValue::Object(inner));
259
260        let json = JsonbValue::Object(outer);
261        let path = JsonPath::parse("$..name").unwrap();
262        let results = json.query(&path);
263        assert_eq!(results.len(), 2);
264    }
265
266    #[test]
267    fn test_query_filter() {
268        let mut item1 = JsonbObject::new();
269        item1.insert("price".into(), JsonbValue::Number(5.0));
270
271        let mut item2 = JsonbObject::new();
272        item2.insert("price".into(), JsonbValue::Number(15.0));
273
274        let mut item3 = JsonbObject::new();
275        item3.insert("price".into(), JsonbValue::Number(8.0));
276
277        let mut root = JsonbObject::new();
278        root.insert(
279            "items".into(),
280            JsonbValue::Array(vec![
281                JsonbValue::Object(item1),
282                JsonbValue::Object(item2),
283                JsonbValue::Object(item3),
284            ]),
285        );
286
287        let json = JsonbValue::Object(root);
288        let path = JsonPath::parse("$.items[?(@.price < 10)]").unwrap();
289        let results = json.query(&path);
290        assert_eq!(results.len(), 2);
291    }
292
293    #[test]
294    fn test_query_first() {
295        let json = make_test_json();
296        let path = JsonPath::parse("$.user.name").unwrap();
297        let result = json.query_first(&path);
298        assert_eq!(result, Some(&JsonbValue::String("Alice".into())));
299    }
300
301    #[test]
302    fn test_query_missing_field() {
303        let json = make_test_json();
304        let path = JsonPath::parse("$.user.email").unwrap();
305        let results = json.query(&path);
306        assert!(results.is_empty());
307    }
308}