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    let pattern = if pattern.contains("\\\\") {
159        pattern.replace("\\\\", "\\")
160    } else {
161        pattern.to_string()
162    };
163
164    pattern.trim_matches(|c| c == '\'' || c == '"').to_string()
165}
166
167fn value<T: Queryable>(state: State<T>) -> State<T> {
168    match state.data {
169        Data::Ref(..) | Data::Value(..) => state,
170        Data::Refs(items) if items.len() == 1 => {
171            State::data(state.root, Data::Ref(items[0].clone()))
172        }
173        _ => State::nothing(state.root),
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use crate::parser::model::Segment;
180    use crate::parser::model::Selector;
181    use crate::parser::model::Test;
182    use crate::parser::model::TestFunction;
183    use crate::query::state::{Data, Pointer, State};
184    use crate::query::test_function::{regex, FnArg};
185    use crate::query::Query;
186    use crate::{arg, q_segment, segment, selector, test, test_fn};
187    use serde_json::json;
188
189    #[test]
190    fn test_len() {
191        let json = json!({"array": [1,2,3]});
192        let state = State::root(&json);
193
194        let query = test_fn!(length arg!(t test!(@ segment!(selector!(array)))));
195        let res = query.process(state);
196
197        assert_eq!(res.ok_val(), Some(json!(3)));
198    }
199
200    #[test]
201    fn test_match_1() {
202        let json = json!({"a": "abc sdgfudsf","b": "abc.*"});
203        let state = State::root(&json);
204
205        let query = test_fn!(match
206            arg!(t test!(@ segment!(selector!(a)))),
207            arg!(t test!(@ segment!(selector!(b))))
208        );
209        let res = query.process(state);
210
211        assert_eq!(res.ok_val(), Some(json!(true)));
212    }
213
214    #[test]
215    fn test_count_1() {
216        let json = json!({"array": [1,2,3]});
217        let state = State::root(&json);
218
219        let query = test_fn!(count arg!(t test!(@ segment!(selector!(array)))));
220        let res = query.process(state);
221
222        assert_eq!(res.ok_val(), Some(json!(1)));
223    }
224
225    #[test]
226    fn test_search() {
227        let json = json!("123");
228        let state = State::root(&json);
229        let reg = State::str("[a-z]+", &json);
230
231        let res = regex(state, reg, true);
232
233        assert_eq!(res.ok_val(), Some(json!(false)));
234    }
235
236    #[test]
237    fn test_match() {
238        let json = json!("bbab");
239        let state = State::root(&json);
240        let reg = State::str("^b.?b$", &json);
241
242        let res = regex(state, reg, false);
243
244        assert_eq!(res.ok_val(), Some(json!(false)));
245    }
246}