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    // Collect every group's outcome in order, then pick the first failure the
76    // way the serial branch above does. Short-circuiting in parallel instead
77    // would surface an arbitrary one of the failing groups' errors, so the same
78    // query could report a different error on each run.
79    let results: Vec<Result<Value, JqlRunnerError>> = groups
80        .par_iter()
81        .map(|group| group_runner(group, json))
82        .collect();
83
84    let result = results.into_iter().collect::<Result<Vec<Value>, _>>()?;
85
86    Ok(json!(result))
87}
88
89/// Takes a slice of references of `Token` and a reference of a JSON `Value`.
90/// Returns a JSON `Value` or an error.
91/// Note: the `GroupSeparator` enum variant is unreachable at this point since
92/// it has been filtered out by any of the public `runner` functions.
93pub(crate) fn group_runner(tokens: &[&Token], json: &Value) -> Result<Value, JqlRunnerError> {
94    tokens
95        .iter()
96        .try_fold((json.clone(), false), |mut outer_acc, &token| {
97            if outer_acc.1 {
98                let piped = outer_acc.1;
99                let array = outer_acc.0.as_array_mut().unwrap();
100                let mut values = Vec::with_capacity(array.len());
101                let mut last_piped = piped;
102
103                for inner_value in array.iter() {
104                    let result = matcher((inner_value.clone(), piped), token)?;
105
106                    values.push(result.0);
107                    last_piped = result.1;
108                }
109
110                Ok((json!(values), last_piped))
111            } else {
112                matcher(outer_acc, token)
113            }
114        })
115        // Drop the `pipe` boolean flag.
116        .map(|(value, _)| value)
117}
118
119/// Internal matcher consumed by the `group_runner` to apply a selection based
120/// on the provided mutable JSON `Value` and the reference of a `Token`.
121/// A `piped` flag is used to keep track of the pipe operators.
122fn matcher(
123    (mut acc, mut piped): (Value, bool),
124    token: &Token,
125) -> Result<(Value, bool), JqlRunnerError> {
126    let result = match token {
127        Token::ArrayIndexSelector(indexes) => get_array_indexes(indexes, &acc),
128        Token::ArrayRangeSelector(range) => get_array_range(range, &mut acc),
129        Token::FlattenOperator => match acc {
130            Value::Array(_) => get_flattened_array(&acc),
131            Value::Object(_) => Ok(get_flattened_object(&acc)),
132            _ => Err(JqlRunnerError::FlattenError(acc)),
133        },
134        Token::KeyOperator => match acc {
135            Value::Array(_) => get_array_as_indexes(&acc),
136            Value::Object(_) => get_object_as_keys(&mut acc),
137            // Return the original value for Null, Bool, Number and String.
138            Value::Bool(bool) => Ok(json!(bool)),
139            Value::Number(number) => Ok(json!(number)),
140            Value::String(string) => Ok(json!(string)),
141            Value::Null => Ok(json!(null)),
142        },
143        Token::GroupSeparator => unreachable!(),
144        Token::KeySelector(key) => get_object_key(key, &acc),
145        Token::LensSelector(lenses) => get_array_lenses(lenses, &mut acc),
146        Token::MultiKeySelector(keys) => get_object_multi_key(keys, &mut acc),
147        Token::ObjectIndexSelector(indexes) => get_object_indexes(indexes, &mut acc),
148        Token::ObjectRangeSelector(range) => get_object_range(range, &mut acc),
149        Token::PipeInOperator => {
150            if !acc.is_array() {
151                return Err(JqlRunnerError::PipeInError(acc));
152            }
153
154            piped = true;
155
156            Ok(acc)
157        }
158        Token::PipeOutOperator => {
159            if !piped {
160                return Err(JqlRunnerError::PipeOutError);
161            }
162
163            piped = false;
164
165            Ok(acc)
166        }
167        Token::TruncateOperator => match acc {
168            Value::Array(_) => Ok(json!([])),
169            Value::Object(_) => Ok(json!({})),
170            Value::Bool(_) | Value::Number(_) | Value::String(_) | Value::Null => Ok(acc),
171        },
172    };
173
174    result.map(|value| (value, piped))
175}
176
177#[cfg(test)]
178mod tests {
179    use jql_parser::{
180        errors::JqlParserError,
181        tokens::{
182            Token,
183            View,
184        },
185    };
186    use serde_json::json;
187
188    use super::raw;
189    use crate::errors::JqlRunnerError;
190
191    #[test]
192    fn check_runner_empty_input_error() {
193        assert_eq!(raw("", &json!("")), Err(JqlRunnerError::EmptyQueryError));
194    }
195
196    #[test]
197    fn check_runner_parsing_error() {
198        assert_eq!(
199            raw(r#""a"b"#, &json!({ "a": 1 })),
200            Err(JqlRunnerError::ParsingError(JqlParserError::ParsingError {
201                tokens: [Token::KeySelector("a".into())].stringify(),
202                unparsed: "b".to_string(),
203            }))
204        );
205    }
206
207    #[test]
208    fn check_runner_no_key_found_error() {
209        let parent = json!({ "a": 1 });
210
211        assert_eq!(
212            raw(r#""b""#, &parent),
213            Err(JqlRunnerError::KeyNotFoundError {
214                key: "b".to_string(),
215                parent
216            })
217        );
218    }
219
220    #[test]
221    fn check_runner_index_not_found_error() {
222        let parent = json!(["a"]);
223
224        assert_eq!(
225            raw("[1]", &parent),
226            Err(JqlRunnerError::IndexOutOfBoundsError { index: 1, parent })
227        );
228    }
229
230    #[test]
231    fn check_runner_success() {
232        assert_eq!(
233            raw(r#""a","b""#, &json!({ "a": 1, "b": 2 })),
234            Ok(json!([1, 2]))
235        );
236        assert_eq!(raw(r#""a""b""#, &json!({ "a": { "b": 2 } })), Ok(json!(2)));
237        assert_eq!(
238            raw("[4,2,0]", &json!(["a", "b", "c", "d", "e"])),
239            Ok(json!(["e", "c", "a"]))
240        );
241    }
242
243    #[test]
244    fn check_runner_pipes() {
245        let value = json!({ "a": [{ "b": { "c": 1 } }, { "b": { "c": 2 }}]});
246
247        assert_eq!(raw(r#""a"|>"b""c"<|[1]"#, &value), Ok(json!(2)));
248    }
249
250    #[test]
251    fn check_runner_truncate() {
252        assert_eq!(raw(r#""a"!"#, &json!({ "a": [1, 2, 3] })), Ok(json!([])));
253        assert_eq!(raw(r#""a"!"#, &json!({ "a": { "b": 1 } })), Ok(json!({})));
254        assert_eq!(raw(r#""a"!"#, &json!({ "a": true })), Ok(json!(true)));
255        assert_eq!(raw(r#""a"!"#, &json!({ "a": 1 })), Ok(json!(1)));
256        assert_eq!(raw(r#""a"!"#, &json!({ "a": "b" })), Ok(json!("b")));
257        assert_eq!(raw(r#""a"!"#, &json!({ "a": null })), Ok(json!(null)));
258        assert_eq!(raw("!", &json!({ "a": null })), Ok(json!({})));
259        assert_eq!(
260            raw(r#""a"!"b""#, &json!({ "a": [1, 2, 3] })),
261            Err(JqlRunnerError::ParsingError(JqlParserError::TruncateError(
262                [
263                    Token::KeySelector("a".into()),
264                    Token::TruncateOperator,
265                    Token::KeySelector("b".into())
266                ]
267                .stringify(),
268            )))
269        );
270    }
271
272    #[test]
273    fn check_runner_lens() {
274        let value = json!([
275            { "a": { "b": { "c": 1 }}},
276            { "a": { "b": { "c": 2 }}},
277        ]);
278
279        assert_eq!(
280            raw(r#"|={"a""b""c"=2}"#, &value),
281            Ok(json!([
282                { "a": { "b": { "c": 2 }}}
283            ]))
284        );
285    }
286
287    #[test]
288    fn check_runner_keys() {
289        let value = json!({ "a": { "b": { "c": { "d": 1 }}}});
290
291        assert_eq!(raw(r#""a""b""c"@"#, &value), Ok(json!(["d"])));
292    }
293
294    #[test]
295    fn check_runner_reports_the_first_failing_group() {
296        // Past eight groups the groups run in parallel; the reported error must
297        // still be the first one by position, and the same on every run.
298        let parent = json!({ "a": 1 });
299        let expected = Err(JqlRunnerError::KeyNotFoundError {
300            key: "x".to_string(),
301            parent: parent.clone(),
302        });
303
304        for _ in 0..64 {
305            assert_eq!(
306                raw(r#""a","x","a","y","a","z","a","w","a""#, &parent),
307                expected
308            );
309        }
310    }
311}