Skip to main content

jql_runner/
runner.rs

1use jql_parser::{
2    group::split,
3    parser::parse,
4    tokens::Token,
5};
6use rayon::prelude::*;
7use serde_json::{
8    Value,
9    json,
10};
11
12use crate::{
13    array::{
14        get_array_as_indexes,
15        get_array_indexes,
16        get_array_lenses,
17        get_array_range,
18        get_flattened_array,
19    },
20    errors::JqlRunnerError,
21    object::{
22        get_flattened_object,
23        get_object_as_keys,
24        get_object_indexes,
25        get_object_key,
26        get_object_multi_key,
27        get_object_range,
28    },
29};
30
31/// Takes a raw input as a slice string to parse and a reference of a JSON
32/// `Value`.
33/// Returns a JSON `Value`.
34///
35/// # Errors
36///
37/// Returns a `JqlRunnerError` on failure.
38pub fn raw(input: &str, json: &Value) -> Result<Value, JqlRunnerError> {
39    if input.is_empty() {
40        return Err(JqlRunnerError::EmptyQueryError);
41    }
42
43    let tokens = parse(input)?;
44
45    token(&tokens, json)
46}
47
48/// Takes a slice of `Tokens` to parse and a reference of a JSON
49/// `Value`.
50/// Returns a JSON `Value`.
51///
52/// # Errors
53///
54/// Returns a `JqlRunnerError` on failure.
55pub fn token(tokens: &[Token], json: &Value) -> Result<Value, JqlRunnerError> {
56    let groups = split(tokens);
57
58    if groups.len() == 1 {
59        return group_runner(&groups[0], json);
60    }
61
62    if groups.len() < 8 {
63        let result =
64            groups
65                .iter()
66                .try_fold(Vec::with_capacity(groups.len()), |mut acc, group| {
67                    acc.push(group_runner(group, json)?);
68
69                    Ok::<Vec<Value>, JqlRunnerError>(acc)
70                })?;
71
72        return Ok(json!(result));
73    }
74
75    let result = groups
76        .par_iter()
77        .try_fold_with(vec![], |mut acc: Vec<Value>, group| {
78            acc.push(group_runner(group, json)?);
79
80            Ok::<Vec<Value>, JqlRunnerError>(acc)
81        })
82        .try_reduce(Vec::new, |mut a, b| {
83            a.extend(b);
84
85            Ok(a)
86        });
87
88    result.map(|group| json!(group))
89}
90
91/// Takes a slice of references of `Token` and a reference of a JSON `Value`.
92/// Returns a JSON `Value` or an error.
93/// Note: the `GroupSeparator` enum variant is unreachable at this point since
94/// it has been filtered out by any of the public `runner` functions.
95pub(crate) fn group_runner(tokens: &[&Token], json: &Value) -> Result<Value, JqlRunnerError> {
96    tokens
97        .iter()
98        // At this level we can use rayon since every token is applied
99        // sequentially.
100        .try_fold((json.clone(), false), |mut outer_acc, &token| {
101            if outer_acc.1 {
102                let piped = outer_acc.1;
103                let array = outer_acc.0.as_array_mut().unwrap();
104
105                // Rayon's thread-spawn overhead (~40 µs) dominates for
106                // lightweight per-element work. Benchmarks sweeping 1–128
107                // elements show serial is consistently faster throughout; at
108                // 128 elements serial costs ~16 µs vs Rayon's ~180 µs. Rayon's
109                // overhead curve for this operation is steep enough that the
110                // break-even lies well above 128 elements.
111                if array.len() < 128 {
112                    let mut values = Vec::with_capacity(array.len());
113                    let mut last_piped = piped;
114
115                    for inner_value in array.iter() {
116                        let r = matcher((inner_value.clone(), piped), token)?;
117
118                        values.push(r.0);
119                        last_piped = r.1;
120                    }
121
122                    return Ok((json!(values), last_piped));
123                }
124
125                let result = array
126                    .par_iter()
127                    .try_fold_with(
128                        (vec![], piped),
129                        |mut inner_acc: (Vec<Value>, bool), inner_value| {
130                            let result = matcher((inner_value.clone(), piped), token)?;
131
132                            inner_acc.0.push(result.0);
133                            inner_acc.1 = result.1;
134
135                            Ok::<(Vec<Value>, bool), JqlRunnerError>(inner_acc)
136                        },
137                    )
138                    .try_reduce(
139                        || (vec![], false),
140                        |mut a, b| {
141                            a.0.extend(b.0);
142
143                            Ok((a.0, b.1))
144                        },
145                    )?;
146
147                Ok((json!(result.0), result.1))
148            } else {
149                matcher(outer_acc, token)
150            }
151        })
152        // Drop the `pipe` boolean flag.
153        .map(|(value, _)| value)
154}
155
156/// Internal matcher consumed by the `group_runner` to apply a selection based
157/// on the provided mutable JSON `Value` and the reference of a `Token`.
158/// A `piped` flag is used to keep track of the pipe operators.
159fn matcher(
160    (mut acc, mut piped): (Value, bool),
161    token: &Token,
162) -> Result<(Value, bool), JqlRunnerError> {
163    let result = match token {
164        Token::ArrayIndexSelector(indexes) => get_array_indexes(indexes, &acc),
165        Token::ArrayRangeSelector(range) => get_array_range(range, &mut acc),
166        Token::FlattenOperator => match acc {
167            Value::Array(_) => get_flattened_array(&acc),
168            Value::Object(_) => Ok(get_flattened_object(&acc)),
169            _ => Err(JqlRunnerError::FlattenError(acc)),
170        },
171        Token::KeyOperator => match acc {
172            Value::Array(_) => get_array_as_indexes(&acc),
173            Value::Object(_) => get_object_as_keys(&mut acc),
174            // Return the original value for Null, Bool, Number and String.
175            Value::Bool(bool) => Ok(json!(bool)),
176            Value::Number(number) => Ok(json!(number)),
177            Value::String(string) => Ok(json!(string)),
178            Value::Null => Ok(json!(null)),
179        },
180        Token::GroupSeparator => unreachable!(),
181        Token::KeySelector(key) => get_object_key(key, &acc),
182        Token::LensSelector(lenses) => get_array_lenses(lenses, &mut acc),
183        Token::MultiKeySelector(keys) => get_object_multi_key(keys, &mut acc),
184        Token::ObjectIndexSelector(indexes) => get_object_indexes(indexes, &mut acc),
185        Token::ObjectRangeSelector(range) => get_object_range(range, &mut acc),
186        Token::PipeInOperator => {
187            if !acc.is_array() {
188                return Err(JqlRunnerError::PipeInError(acc));
189            }
190
191            piped = true;
192
193            Ok(acc)
194        }
195        Token::PipeOutOperator => {
196            if !piped {
197                return Err(JqlRunnerError::PipeOutError);
198            }
199
200            piped = false;
201
202            Ok(acc)
203        }
204        Token::TruncateOperator => match acc {
205            Value::Array(_) => Ok(json!([])),
206            Value::Object(_) => Ok(json!({})),
207            Value::Bool(_) | Value::Number(_) | Value::String(_) | Value::Null => Ok(acc),
208        },
209    };
210
211    result.map(|value| (value, piped))
212}
213
214#[cfg(test)]
215mod tests {
216    use jql_parser::{
217        errors::JqlParserError,
218        tokens::{
219            Token,
220            View,
221        },
222    };
223    use serde_json::json;
224
225    use super::raw;
226    use crate::errors::JqlRunnerError;
227
228    #[test]
229    fn check_runner_empty_input_error() {
230        assert_eq!(raw("", &json!("")), Err(JqlRunnerError::EmptyQueryError));
231    }
232
233    #[test]
234    fn check_runner_parsing_error() {
235        assert_eq!(
236            raw(r#""a"b"#, &json!({ "a": 1 })),
237            Err(JqlRunnerError::ParsingError(JqlParserError::ParsingError {
238                tokens: [Token::KeySelector("a")].stringify(),
239                unparsed: "b".to_string(),
240            }))
241        );
242    }
243
244    #[test]
245    fn check_runner_no_key_found_error() {
246        let parent = json!({ "a": 1 });
247
248        assert_eq!(
249            raw(r#""b""#, &parent),
250            Err(JqlRunnerError::KeyNotFoundError {
251                key: "b".to_string(),
252                parent
253            })
254        );
255    }
256
257    #[test]
258    fn check_runner_index_not_found_error() {
259        let parent = json!(["a"]);
260
261        assert_eq!(
262            raw("[1]", &parent),
263            Err(JqlRunnerError::IndexOutOfBoundsError { index: 1, parent })
264        );
265    }
266
267    #[test]
268    fn check_runner_success() {
269        assert_eq!(
270            raw(r#""a","b""#, &json!({ "a": 1, "b": 2 })),
271            Ok(json!([1, 2]))
272        );
273        assert_eq!(raw(r#""a""b""#, &json!({ "a": { "b": 2 } })), Ok(json!(2)));
274        assert_eq!(
275            raw("[4,2,0]", &json!(["a", "b", "c", "d", "e"])),
276            Ok(json!(["e", "c", "a"]))
277        );
278    }
279
280    #[test]
281    fn check_runner_pipes() {
282        let value = json!({ "a": [{ "b": { "c": 1 } }, { "b": { "c": 2 }}]});
283
284        assert_eq!(raw(r#""a"|>"b""c"<|[1]"#, &value), Ok(json!(2)));
285    }
286
287    #[test]
288    fn check_runner_truncate() {
289        assert_eq!(raw(r#""a"!"#, &json!({ "a": [1, 2, 3] })), Ok(json!([])));
290        assert_eq!(raw(r#""a"!"#, &json!({ "a": { "b": 1 } })), Ok(json!({})));
291        assert_eq!(raw(r#""a"!"#, &json!({ "a": true })), Ok(json!(true)));
292        assert_eq!(raw(r#""a"!"#, &json!({ "a": 1 })), Ok(json!(1)));
293        assert_eq!(raw(r#""a"!"#, &json!({ "a": "b" })), Ok(json!("b")));
294        assert_eq!(raw(r#""a"!"#, &json!({ "a": null })), Ok(json!(null)));
295        assert_eq!(raw("!", &json!({ "a": null })), Ok(json!({})));
296        assert_eq!(
297            raw(r#""a"!"b""#, &json!({ "a": [1, 2, 3] })),
298            Err(JqlRunnerError::ParsingError(JqlParserError::TruncateError(
299                [
300                    Token::KeySelector("a"),
301                    Token::TruncateOperator,
302                    Token::KeySelector("b")
303                ]
304                .stringify(),
305            )))
306        );
307    }
308
309    #[test]
310    fn check_runner_lens() {
311        let value = json!([
312            { "a": { "b": { "c": 1 }}},
313            { "a": { "b": { "c": 2 }}},
314        ]);
315
316        assert_eq!(
317            raw(r#"|={"a""b""c"=2}"#, &value),
318            Ok(json!([
319                { "a": { "b": { "c": 2 }}}
320            ]))
321        );
322    }
323
324    #[test]
325    fn check_runner_keys() {
326        let value = json!({ "a": { "b": { "c": { "d": 1 }}}});
327
328        assert_eq!(raw(r#""a""b""c"@"#, &value), Ok(json!(["d"])));
329    }
330}