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
142/// Translates an I-Regexp ([RFC9485]) into the syntax of the `regex` crate.
143///
144/// An I-Regexp `.` matches any character except `\n` and `\r` (RFC9485, section 5.3),
145/// but the crate's `.` matches `\r`, so `.` outside a character class becomes `[^\n\r]`.
146/// For match() the whole pattern is anchored, so `a|b` does not match "ab".
147fn prepare_regex(pattern: String, substring: bool) -> String {
148    let mut translated = String::with_capacity(pattern.len());
149    let mut chars = pattern.chars();
150    let mut in_class = false;
151    while let Some(c) = chars.next() {
152        match c {
153            '\\' => {
154                translated.push(c);
155                if let Some(escaped) = chars.next() {
156                    translated.push(escaped);
157                }
158            }
159            '[' if !in_class => {
160                in_class = true;
161                translated.push(c);
162            }
163            ']' if in_class => {
164                in_class = false;
165                translated.push(c);
166            }
167            '.' if !in_class => translated.push_str(r"[^\n\r]"),
168            _ => translated.push(c),
169        }
170    }
171
172    if substring {
173        translated
174    } else {
175        format!("^(?:{translated})$")
176    }
177}
178
179fn value<T: Queryable>(state: State<T>) -> State<T> {
180    match state.data {
181        Data::Ref(..) | Data::Value(..) => state,
182        Data::Refs(items) if items.len() == 1 => {
183            State::data(state.root, Data::Ref(items[0].clone()))
184        }
185        _ => State::nothing(state.root),
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use crate::parser::model::Segment;
192    use crate::parser::model::Selector;
193    use crate::parser::model::Test;
194    use crate::parser::model::TestFunction;
195    use crate::query::state::{Data, Pointer, State};
196    use crate::query::test_function::{regex, FnArg};
197    use crate::query::Query;
198    use crate::{arg, q_segment, segment, selector, test, test_fn};
199    use serde_json::json;
200
201    #[test]
202    fn test_len() {
203        let json = json!({"array": [1,2,3]});
204        let state = State::root(&json);
205
206        let query = test_fn!(length arg!(t test!(@ segment!(selector!(array)))));
207        let res = query.process(state);
208
209        assert_eq!(res.ok_val(), Some(json!(3)));
210    }
211
212    #[test]
213    fn test_match_1() {
214        let json = json!({"a": "abc sdgfudsf","b": "abc.*"});
215        let state = State::root(&json);
216
217        let query = test_fn!(match
218            arg!(t test!(@ segment!(selector!(a)))),
219            arg!(t test!(@ segment!(selector!(b))))
220        );
221        let res = query.process(state);
222
223        assert_eq!(res.ok_val(), Some(json!(true)));
224    }
225
226    #[test]
227    fn test_count_1() {
228        let json = json!({"array": [1,2,3]});
229        let state = State::root(&json);
230
231        let query = test_fn!(count arg!(t test!(@ segment!(selector!(array)))));
232        let res = query.process(state);
233
234        assert_eq!(res.ok_val(), Some(json!(1)));
235    }
236
237    #[test]
238    fn test_search() {
239        let json = json!("123");
240        let state = State::root(&json);
241        let reg = State::str("[a-z]+", &json);
242
243        let res = regex(state, reg, true);
244
245        assert_eq!(res.ok_val(), Some(json!(false)));
246    }
247
248    #[test]
249    fn test_match() {
250        let json = json!("bbab");
251        let state = State::root(&json);
252        let reg = State::str("^b.?b$", &json);
253
254        let res = regex(state, reg, false);
255
256        assert_eq!(res.ok_val(), Some(json!(false)));
257    }
258}