jarq 0.2.3

An interactive jq-like JSON query tool with a TUI
Documentation
use rayon::prelude::*;
use serde_json::Value;

use crate::error::EvalError;
use super::ast::{Filter, ObjectKey};
use super::builtins;

/// Threshold for parallel array operations
const PARALLEL_THRESHOLD: usize = 10_000;

pub fn eval(filter: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
    match filter {
        Filter::Identity => Ok(vec![value.clone()]),
        Filter::Field(name) => match value {
            Value::Object(map) => Ok(vec![map.get(name).cloned().unwrap_or(Value::Null)]),
            _ => Ok(vec![Value::Null]),
        },
        Filter::Index(n) => match value {
            Value::Array(arr) => {
                let idx = if *n >= 0 {
                    *n as usize
                } else {
                    arr.len()
                        .checked_sub((-*n) as usize)
                        .unwrap_or(usize::MAX)
                };
                Ok(vec![arr.get(idx).cloned().unwrap_or(Value::Null)])
            }
            _ => Ok(vec![Value::Null]),
        },
        Filter::Iterate => match value {
            Value::Array(arr) if arr.len() >= PARALLEL_THRESHOLD => {
                Ok(arr.par_iter().cloned().collect())
            }
            Value::Array(arr) => Ok(arr.clone()),
            Value::Object(map) => Ok(map.values().cloned().collect()),
            _ => Err(EvalError::CannotIterate {
                value: value.clone(),
                position: 0, // Position will be determined by caller
            }),
        },
        Filter::Pipe(left, right) => {
            let left_results = eval(left, value)?;
            let mut results = Vec::new();
            for v in left_results {
                results.extend(eval(right, &v)?);
            }
            Ok(results)
        }
        Filter::Builtin(b) => builtins::eval(b, value),
        Filter::Array(element_filters) => {
            let mut array_elements = Vec::new();
            for elem_filter in element_filters {
                let results = eval(elem_filter, value)?;
                // Collect all results (handles .[] producing multiple values)
                array_elements.extend(results);
            }
            Ok(vec![Value::Array(array_elements)])
        }
        Filter::Object(pairs) => {
            let mut obj = serde_json::Map::new();
            for (key, value_filter) in pairs {
                // Resolve key (static or dynamic)
                let key_str = match key {
                    ObjectKey::Static(s) => s.clone(),
                    ObjectKey::Dynamic(key_filter) => {
                        let key_results = eval(key_filter, value)?;
                        match key_results.into_iter().next() {
                            Some(Value::String(s)) => s,
                            Some(other) => {
                                return Err(EvalError::TypeError {
                                    message: format!(
                                        "object key must be string, got {}",
                                        type_name(&other)
                                    ),
                                    position: 0,
                                });
                            }
                            None => {
                                return Err(EvalError::TypeError {
                                    message: "object key expression produced no value".to_string(),
                                    position: 0,
                                });
                            }
                        }
                    }
                };

                let results = eval(value_filter, value)?;
                // Take first result for object values
                let val = results.into_iter().next().unwrap_or(Value::Null);
                obj.insert(key_str, val);
            }
            Ok(vec![Value::Object(obj)])
        }
    }
}

fn type_name(v: &Value) -> &'static str {
    match v {
        Value::Null => "null",
        Value::Bool(_) => "boolean",
        Value::Number(_) => "number",
        Value::String(_) => "string",
        Value::Array(_) => "array",
        Value::Object(_) => "object",
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    // Identity tests
    #[test]
    fn test_identity() {
        let value = json!({"name": "alice"});
        let result = eval(&Filter::Identity, &value).unwrap();
        assert_eq!(result, vec![value]);
    }

    // Field access tests
    #[test]
    fn test_field_access() {
        let value = json!({"name": "alice", "age": 30});
        let result = eval(&Filter::Field("name".to_string()), &value).unwrap();
        assert_eq!(result, vec![json!("alice")]);
    }

    #[test]
    fn test_field_missing() {
        let value = json!({"name": "alice"});
        let result = eval(&Filter::Field("missing".to_string()), &value).unwrap();
        assert_eq!(result, vec![Value::Null]);
    }

    #[test]
    fn test_field_on_non_object() {
        let result = eval(&Filter::Field("foo".to_string()), &json!("string")).unwrap();
        assert_eq!(result, vec![Value::Null]);

        let result = eval(&Filter::Field("foo".to_string()), &json!(123)).unwrap();
        assert_eq!(result, vec![Value::Null]);

        let result = eval(&Filter::Field("foo".to_string()), &json!(null)).unwrap();
        assert_eq!(result, vec![Value::Null]);

        let result = eval(&Filter::Field("foo".to_string()), &json!([1, 2, 3])).unwrap();
        assert_eq!(result, vec![Value::Null]);
    }

    #[test]
    fn test_nested_field() {
        let value = json!({"user": {"name": "alice", "address": {"city": "NYC"}}});
        let filter = Filter::Pipe(
            Box::new(Filter::Field("user".to_string())),
            Box::new(Filter::Field("name".to_string())),
        );
        let result = eval(&filter, &value).unwrap();
        assert_eq!(result, vec![json!("alice")]);
    }

    #[test]
    fn test_deeply_nested_field() {
        let value = json!({"user": {"address": {"city": "NYC"}}});
        let filter = Filter::Pipe(
            Box::new(Filter::Pipe(
                Box::new(Filter::Field("user".to_string())),
                Box::new(Filter::Field("address".to_string())),
            )),
            Box::new(Filter::Field("city".to_string())),
        );
        let result = eval(&filter, &value).unwrap();
        assert_eq!(result, vec![json!("NYC")]);
    }

    #[test]
    fn test_nested_field_missing() {
        let value = json!({"user": {}});
        let filter = Filter::Pipe(
            Box::new(Filter::Field("user".to_string())),
            Box::new(Filter::Field("name".to_string())),
        );
        let result = eval(&filter, &value).unwrap();
        assert_eq!(result, vec![Value::Null]);
    }

    #[test]
    fn test_field_on_nested_null() {
        let value = json!({"user": null});
        let filter = Filter::Pipe(
            Box::new(Filter::Field("user".to_string())),
            Box::new(Filter::Field("name".to_string())),
        );
        let result = eval(&filter, &value).unwrap();
        assert_eq!(result, vec![Value::Null]);
    }

    // Index tests
    #[test]
    fn test_index_positive() {
        let value = json!([10, 20, 30]);
        assert_eq!(eval(&Filter::Index(0), &value).unwrap(), vec![json!(10)]);
        assert_eq!(eval(&Filter::Index(1), &value).unwrap(), vec![json!(20)]);
        assert_eq!(eval(&Filter::Index(2), &value).unwrap(), vec![json!(30)]);
    }

    #[test]
    fn test_index_negative() {
        let value = json!([10, 20, 30]);
        assert_eq!(eval(&Filter::Index(-1), &value).unwrap(), vec![json!(30)]);
        assert_eq!(eval(&Filter::Index(-2), &value).unwrap(), vec![json!(20)]);
        assert_eq!(eval(&Filter::Index(-3), &value).unwrap(), vec![json!(10)]);
    }

    #[test]
    fn test_index_out_of_bounds() {
        let value = json!([10, 20, 30]);
        assert_eq!(eval(&Filter::Index(10), &value).unwrap(), vec![Value::Null]);
        assert_eq!(
            eval(&Filter::Index(-10), &value).unwrap(),
            vec![Value::Null]
        );
    }

    #[test]
    fn test_index_on_non_array() {
        assert_eq!(
            eval(&Filter::Index(0), &json!({"a": 1})).unwrap(),
            vec![Value::Null]
        );
        assert_eq!(
            eval(&Filter::Index(0), &json!("string")).unwrap(),
            vec![Value::Null]
        );
        assert_eq!(
            eval(&Filter::Index(0), &json!(123)).unwrap(),
            vec![Value::Null]
        );
        assert_eq!(
            eval(&Filter::Index(0), &json!(null)).unwrap(),
            vec![Value::Null]
        );
    }

    // Iterate tests
    #[test]
    fn test_iterate_array() {
        let value = json!([1, 2, 3]);
        let result = eval(&Filter::Iterate, &value).unwrap();
        assert_eq!(result, vec![json!(1), json!(2), json!(3)]);
    }

    #[test]
    fn test_iterate_object() {
        let value = json!({"a": 1, "b": 2});
        let result = eval(&Filter::Iterate, &value).unwrap();
        // Object iteration returns values (order may vary, so just check contents)
        assert_eq!(result.len(), 2);
        assert!(result.contains(&json!(1)));
        assert!(result.contains(&json!(2)));
    }

    #[test]
    fn test_iterate_empty_array() {
        let value = json!([]);
        let result = eval(&Filter::Iterate, &value).unwrap();
        assert_eq!(result, Vec::<Value>::new());
    }

    #[test]
    fn test_iterate_empty_object() {
        let value = json!({});
        let result = eval(&Filter::Iterate, &value).unwrap();
        assert_eq!(result, Vec::<Value>::new());
    }

    #[test]
    fn test_iterate_non_iterable_errors() {
        assert!(matches!(
            eval(&Filter::Iterate, &json!("string")),
            Err(EvalError::CannotIterate { .. })
        ));
        assert!(matches!(
            eval(&Filter::Iterate, &json!(123)),
            Err(EvalError::CannotIterate { .. })
        ));
        assert!(matches!(
            eval(&Filter::Iterate, &json!(null)),
            Err(EvalError::CannotIterate { .. })
        ));
        assert!(matches!(
            eval(&Filter::Iterate, &json!(true)),
            Err(EvalError::CannotIterate { .. })
        ));
    }

    // Chaining tests
    #[test]
    fn test_chain_field_index() {
        let value = json!({"items": [1, 2, 3]});
        let filter = Filter::Pipe(
            Box::new(Filter::Field("items".to_string())),
            Box::new(Filter::Index(0)),
        );
        assert_eq!(eval(&filter, &value).unwrap(), vec![json!(1)]);
    }

    #[test]
    fn test_chain_index_field() {
        let value = json!([{"id": 1}, {"id": 2}]);
        let filter = Filter::Pipe(
            Box::new(Filter::Index(0)),
            Box::new(Filter::Field("id".to_string())),
        );
        assert_eq!(eval(&filter, &value).unwrap(), vec![json!(1)]);
    }

    #[test]
    fn test_chain_iterate_field() {
        let value = json!({"items": [{"id": 1}, {"id": 2}]});
        let filter = Filter::Pipe(
            Box::new(Filter::Pipe(
                Box::new(Filter::Field("items".to_string())),
                Box::new(Filter::Iterate),
            )),
            Box::new(Filter::Field("id".to_string())),
        );
        assert_eq!(eval(&filter, &value).unwrap(), vec![json!(1), json!(2)]);
    }

    // Quoted field tests
    #[test]
    fn test_quoted_field_special_chars() {
        let value = json!({"foo-bar": 1, "with space": 2});
        assert_eq!(
            eval(&Filter::Field("foo-bar".to_string()), &value).unwrap(),
            vec![json!(1)]
        );
        assert_eq!(
            eval(&Filter::Field("with space".to_string()), &value).unwrap(),
            vec![json!(2)]
        );
    }

    #[test]
    fn test_quoted_field_empty() {
        let value = json!({"": 42});
        assert_eq!(
            eval(&Filter::Field("".to_string()), &value).unwrap(),
            vec![json!(42)]
        );
    }

    #[test]
    fn test_quoted_field_unicode() {
        let value = json!({"日本語": "Japanese"});
        assert_eq!(
            eval(&Filter::Field("日本語".to_string()), &value).unwrap(),
            vec![json!("Japanese")]
        );
    }

    // Explicit pipe tests (verifies eval handles parsed explicit pipes correctly)
    #[test]
    fn test_pipe_explicit() {
        let value = json!({"users": [{"name": "alice"}, {"name": "bob"}]});
        // .users | .[] | .name
        let filter = Filter::Pipe(
            Box::new(Filter::Pipe(
                Box::new(Filter::Field("users".to_string())),
                Box::new(Filter::Iterate),
            )),
            Box::new(Filter::Field("name".to_string())),
        );
        assert_eq!(
            eval(&filter, &value).unwrap(),
            vec![json!("alice"), json!("bob")]
        );
    }
}