jarq 0.8.3

An interactive jq-like JSON query tool with a TUI
Documentation
//! Array operations: first, last, reverse, sort, unique, flatten

use rayon::prelude::*;
use simd_json::OwnedValue as Value;
use simd_json::StaticNode;

use super::{json_cmp, type_error};
use crate::error::EvalError;
use crate::filter::PARALLEL_THRESHOLD;
use crate::utils::type_name;

pub fn eval_first(value: &Value) -> Result<Value, EvalError> {
    match value {
        Value::Array(arr) => Ok(arr
            .first()
            .cloned()
            .unwrap_or(Value::Static(StaticNode::Null))),
        _ => Err(type_error(format!(
            "cannot index {} with number",
            type_name(value)
        ))),
    }
}

pub fn eval_last(value: &Value) -> Result<Value, EvalError> {
    match value {
        Value::Array(arr) => Ok(arr
            .last()
            .cloned()
            .unwrap_or(Value::Static(StaticNode::Null))),
        _ => Err(type_error(format!(
            "cannot index {} with number",
            type_name(value)
        ))),
    }
}

pub fn eval_reverse(value: &Value) -> Result<Value, EvalError> {
    match value {
        Value::Array(arr) => {
            let reversed: Vec<Value> = arr.iter().rev().cloned().collect();
            Ok(Value::Array(Box::new(reversed)))
        }
        _ => Err(type_error(format!(
            "{} cannot be reversed, as it is not an array",
            type_name(value)
        ))),
    }
}

pub fn eval_sort(value: &Value) -> Result<Value, EvalError> {
    match value {
        Value::Array(arr) if arr.len() >= PARALLEL_THRESHOLD => {
            let mut sorted: Vec<Value> = arr.to_vec();
            sorted.par_sort_by(json_cmp);
            Ok(Value::Array(Box::new(sorted)))
        }
        Value::Array(arr) => {
            let mut sorted: Vec<Value> = arr.to_vec();
            sorted.sort_by(json_cmp);
            Ok(Value::Array(Box::new(sorted)))
        }
        _ => Err(type_error(format!(
            "{} cannot be sorted, as it is not an array",
            type_name(value)
        ))),
    }
}

pub fn eval_unique(value: &Value) -> Result<Value, EvalError> {
    match value {
        Value::Array(arr) => {
            let mut seen = Vec::new();
            for v in arr.iter() {
                if !seen.contains(v) {
                    seen.push(v.clone());
                }
            }
            Ok(Value::Array(Box::new(seen)))
        }
        _ => Err(type_error(format!(
            "{} cannot have unique elements taken, as it is not an array",
            type_name(value)
        ))),
    }
}

pub fn eval_flatten(value: &Value) -> Result<Value, EvalError> {
    match value {
        Value::Array(arr) => {
            let mut flat = Vec::new();
            for v in arr.iter() {
                match v {
                    Value::Array(inner) => flat.extend(inner.iter().cloned()),
                    other => flat.push(other.clone()),
                }
            }
            Ok(Value::Array(Box::new(flat)))
        }
        _ => Err(type_error(format!(
            "{} cannot be flattened, as it is not an array",
            type_name(value)
        ))),
    }
}

#[cfg(test)]
mod tests {
    use crate::filter::builtins::{Builtin, eval};
    use simd_json::json;

    #[test]
    fn test_first() {
        assert_eq!(
            eval(&Builtin::First, &json!([1, 2, 3])).unwrap(),
            vec![json!(1)]
        );
    }

    #[test]
    fn test_first_empty() {
        assert_eq!(
            eval(&Builtin::First, &json!([])).unwrap(),
            vec![json!(null)]
        );
    }

    #[test]
    fn test_last() {
        assert_eq!(
            eval(&Builtin::Last, &json!([1, 2, 3])).unwrap(),
            vec![json!(3)]
        );
    }

    #[test]
    fn test_last_empty() {
        assert_eq!(eval(&Builtin::Last, &json!([])).unwrap(), vec![json!(null)]);
    }

    #[test]
    fn test_reverse() {
        assert_eq!(
            eval(&Builtin::Reverse, &json!([1, 2, 3])).unwrap(),
            vec![json!([3, 2, 1])]
        );
    }

    #[test]
    fn test_reverse_empty() {
        assert_eq!(
            eval(&Builtin::Reverse, &json!([])).unwrap(),
            vec![json!([])]
        );
    }

    #[test]
    fn test_sort_numbers() {
        assert_eq!(
            eval(&Builtin::Sort, &json!([3, 1, 2])).unwrap(),
            vec![json!([1, 2, 3])]
        );
    }

    #[test]
    fn test_sort_strings() {
        assert_eq!(
            eval(&Builtin::Sort, &json!(["banana", "apple", "cherry"])).unwrap(),
            vec![json!(["apple", "banana", "cherry"])]
        );
    }

    #[test]
    fn test_sort_mixed() {
        // jq order: null < false < true < numbers < strings
        assert_eq!(
            eval(&Builtin::Sort, &json!([1, null, "a", false, true])).unwrap(),
            vec![json!([null, false, true, 1, "a"])]
        );
    }

    #[test]
    fn test_sort_objects() {
        // Objects are sorted by keys first, then by values
        assert_eq!(
            eval(&Builtin::Sort, &json!([{"a": 2}, {"a": 1}])).unwrap(),
            vec![json!([{"a": 1}, {"a": 2}])]
        );
    }

    #[test]
    fn test_sort_objects_different_keys() {
        // Objects with fewer keys come first, then compared by key names
        assert_eq!(
            eval(&Builtin::Sort, &json!([{"b": 1}, {"a": 1}])).unwrap(),
            vec![json!([{"a": 1}, {"b": 1}])]
        );
        assert_eq!(
            eval(&Builtin::Sort, &json!([{"a": 1, "b": 2}, {"a": 1}])).unwrap(),
            vec![json!([{"a": 1}, {"a": 1, "b": 2}])]
        );
    }

    #[test]
    fn test_unique() {
        assert_eq!(
            eval(&Builtin::Unique, &json!([1, 2, 1, 3, 2, 1])).unwrap(),
            vec![json!([1, 2, 3])]
        );
    }

    #[test]
    fn test_unique_preserves_order() {
        assert_eq!(
            eval(&Builtin::Unique, &json!([3, 1, 2, 1, 3])).unwrap(),
            vec![json!([3, 1, 2])]
        );
    }

    #[test]
    fn test_flatten() {
        assert_eq!(
            eval(&Builtin::Flatten, &json!([[1, 2], [3, 4]])).unwrap(),
            vec![json!([1, 2, 3, 4])]
        );
    }

    #[test]
    fn test_flatten_mixed() {
        assert_eq!(
            eval(&Builtin::Flatten, &json!([[1, 2], 3, [4]])).unwrap(),
            vec![json!([1, 2, 3, 4])]
        );
    }

    #[test]
    fn test_flatten_one_level() {
        // flatten only goes one level deep
        assert_eq!(
            eval(&Builtin::Flatten, &json!([[[1]], [2]])).unwrap(),
            vec![json!([[1], 2])]
        );
    }

    // Error tests
    #[test]
    fn test_sort_non_array_errors() {
        assert!(eval(&Builtin::Sort, &json!("hello")).is_err());
        assert!(eval(&Builtin::Sort, &json!(42)).is_err());
        assert!(eval(&Builtin::Sort, &json!({"a": 1})).is_err());
    }

    #[test]
    fn test_first_non_array_errors() {
        assert!(eval(&Builtin::First, &json!("hello")).is_err());
        assert!(eval(&Builtin::First, &json!({"a": 1})).is_err());
    }

    #[test]
    fn test_reverse_non_array_errors() {
        assert!(eval(&Builtin::Reverse, &json!("hello")).is_err());
    }

    #[test]
    fn test_unique_non_array_errors() {
        assert!(eval(&Builtin::Unique, &json!("hello")).is_err());
    }

    #[test]
    fn test_flatten_non_array_errors() {
        assert!(eval(&Builtin::Flatten, &json!("hello")).is_err());
    }
}