Skip to main content

jsonpath_rust/query/
test_function.rs

1use crate::parser::model::{FnArg, TestFunction};
2use crate::query::queryable::Queryable;
3use crate::query::state::{Data, Pointer, State};
4use crate::query::Query;
5use regex::Regex;
6use std::borrow::Cow;
7
8impl TestFunction {
9    pub fn apply<'a, T: Queryable>(&self, state: State<'a, T>) -> State<'a, T> {
10        match self {
11            TestFunction::Length(arg) => length(arg.process(state)),
12            TestFunction::Count(arg) => count(arg.process(state)),
13            TestFunction::Match(lhs, rhs) => {
14                regex(lhs.process(state.clone()), rhs.process(state), false)
15            }
16            TestFunction::Search(lhs, rhs) => {
17                regex(lhs.process(state.clone()), rhs.process(state), true)
18            }
19            TestFunction::Custom(name, args) => custom(name, args, state),
20            TestFunction::Value(arg) => value(arg.process(state)),
21            _ => State::nothing(state.root),
22        }
23    }
24}
25
26impl Query for FnArg {
27    fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> {
28        match self {
29            FnArg::Literal(lit) => lit.process(step),
30            FnArg::Test(test) => test.process(step),
31            FnArg::Filter(filter) => filter.process(step),
32        }
33    }
34}
35
36impl Query for TestFunction {
37    fn process<'a, T: Queryable>(&self, step: State<'a, T>) -> State<'a, T> {
38        self.apply(step)
39    }
40}
41
42fn custom<'a, T: Queryable>(name: &str, args: &Vec<FnArg>, state: State<'a, T>) -> State<'a, T> {
43    let args = args
44        .into_iter()
45        .map(|v| v.process(state.clone()))
46        .flat_map(|v| match v.data {
47            Data::Value(v) => vec![Cow::Owned(v)],
48            Data::Ref(Pointer { inner, .. }) => vec![Cow::Borrowed(inner)],
49            Data::Refs(v) => v.into_iter().map(|v| Cow::Borrowed(v.inner)).collect(),
50            _ => vec![],
51        })
52        .collect::<Vec<_>>();
53
54    State::data(
55        state.root,
56        Data::Value(Queryable::extension_custom(name, args)),
57    )
58}
59
60/// Returns the length/size of the object.
61///
62/// # Returns
63///
64/// Returns a `Progress` enum containing either:
65/// - `Progress::Data` with a vector of references to self and the query path for strings/arrays/objects
66/// - `Progress::Nothing` for other types
67///
68/// The returned length follows JSON path length() function semantics based on the type:
69/// - String type: Number of Unicode scalar values
70/// - Array type: Number of elements
71/// - Object type: Number of members
72/// - Other types: Nothing
73fn length<T: Queryable>(state: State<T>) -> State<T> {
74    let from_item = |item: &T| {
75        if let Some(v) = item.as_str() {
76            State::i64(v.chars().count() as i64, state.root)
77        } else if let Some(items) = item.as_array() {
78            State::i64(items.len() as i64, state.root)
79        } else if let Some(items) = item.as_object() {
80            State::i64(items.len() as i64, state.root)
81        } else {
82            State::nothing(state.root)
83        }
84    };
85
86    match state.data {
87        Data::Ref(Pointer { inner, .. }) => from_item(inner),
88        Data::Refs(items) => State::i64(items.len() as i64, state.root),
89        Data::Value(item) => from_item(&item),
90        Data::Nothing => State::nothing(state.root),
91    }
92}
93
94/// The count() function extension provides a way
95/// to obtain the number of nodes in a nodelist
96/// and make that available for further processing in the filter expression
97fn count<T: Queryable>(state: State<T>) -> State<T> {
98    let to_state = |count: i64| State::i64(count, state.root);
99
100    match state.data {
101        Data::Ref(..) | Data::Value(..) => to_state(1),
102        Data::Refs(items) => to_state(items.len() as i64),
103        Data::Nothing => State::nothing(state.root),
104    }
105}
106/// The match() function extension provides
107/// a way to check whether (the entirety of; see Section 2.4.7)
108/// a given string matches a given regular expression,
109/// which is in the form described in [RFC9485].
110///
111/// Its arguments are instances of ValueType
112/// (possibly taken from a singular query,
113/// as for the first argument in the example above).
114/// If the first argument is not a string
115/// or the second argument is not a string conforming to [RFC9485],
116/// the result is LogicalFalse. Otherwise, the string that is the first argument is matched against
117/// the I-Regexp contained in the string that is the second argument; the result is LogicalTrue
118/// if the string matches the I-Regexp and is LogicalFalse otherwise.
119fn regex<'a, T: Queryable>(lhs: State<'a, T>, rhs: State<'a, T>, substr: bool) -> State<'a, T> {
120    let to_state = |b| State::bool(b, lhs.root);
121    let regex = |v: &str, r: Regex| {
122        if substr {
123            r.find(v).is_some()
124        } else {
125            r.is_match(v)
126        }
127    };
128    let to_str = |s: State<'a, T>| match s.data {
129        Data::Value(v) => v.as_str().map(|s| s.to_string()),
130        Data::Ref(Pointer { inner, .. }) => inner.as_str().map(|s| s.to_string()),
131        _ => None,
132    };
133
134    match (to_str(lhs), to_str(rhs)) {
135        (Some(lhs), Some(rhs)) => Regex::new(&prepare_regex(rhs, substr))
136            .map(|re| to_state(regex(&lhs, re)))
137            .unwrap_or(to_state(false)),
138        _ => to_state(false),
139    }
140}
141
142fn prepare_regex(pattern: String, substring: bool) -> String {
143    let pattern = if !substring {
144        let pattern = if pattern.starts_with('^') {
145            pattern
146        } else {
147            format!("^{}", pattern)
148        };
149        let pattern = if pattern.ends_with('$') {
150            pattern
151        } else {
152            format!("{}$", pattern)
153        };
154        pattern
155    } else {
156        pattern.to_string()
157    };
158    pattern
159}
160
161fn value<T: Queryable>(state: State<T>) -> State<T> {
162    match state.data {
163        Data::Ref(..) | Data::Value(..) => state,
164        Data::Refs(items) if items.len() == 1 => {
165            State::data(state.root, Data::Ref(items[0].clone()))
166        }
167        _ => State::nothing(state.root),
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use crate::parser::model::Segment;
174    use crate::parser::model::Selector;
175    use crate::parser::model::Test;
176    use crate::parser::model::TestFunction;
177    use crate::query::state::{Data, Pointer, State};
178    use crate::query::test_function::{regex, FnArg};
179    use crate::query::Query;
180    use crate::{arg, q_segment, segment, selector, test, test_fn};
181    use serde_json::json;
182
183    #[test]
184    fn test_len() {
185        let json = json!({"array": [1,2,3]});
186        let state = State::root(&json);
187
188        let query = test_fn!(length arg!(t test!(@ segment!(selector!(array)))));
189        let res = query.process(state);
190
191        assert_eq!(res.ok_val(), Some(json!(3)));
192    }
193
194    #[test]
195    fn test_match_1() {
196        let json = json!({"a": "abc sdgfudsf","b": "abc.*"});
197        let state = State::root(&json);
198
199        let query = test_fn!(match
200            arg!(t test!(@ segment!(selector!(a)))),
201            arg!(t test!(@ segment!(selector!(b))))
202        );
203        let res = query.process(state);
204
205        assert_eq!(res.ok_val(), Some(json!(true)));
206    }
207
208    #[test]
209    fn test_count_1() {
210        let json = json!({"array": [1,2,3]});
211        let state = State::root(&json);
212
213        let query = test_fn!(count arg!(t test!(@ segment!(selector!(array)))));
214        let res = query.process(state);
215
216        assert_eq!(res.ok_val(), Some(json!(1)));
217    }
218
219    #[test]
220    fn test_search() {
221        let json = json!("123");
222        let state = State::root(&json);
223        let reg = State::str("[a-z]+", &json);
224
225        let res = regex(state, reg, true);
226
227        assert_eq!(res.ok_val(), Some(json!(false)));
228    }
229
230    #[test]
231    fn test_match() {
232        let json = json!("bbab");
233        let state = State::root(&json);
234        let reg = State::str("^b.?b$", &json);
235
236        let res = regex(state, reg, false);
237
238        assert_eq!(res.ok_val(), Some(json!(false)));
239    }
240}