jarq 0.8.2

An interactive jq-like JSON query tool with a TUI
Documentation
//! Comparison operations for JSON values.

use simd_json::OwnedValue as Value;
use simd_json::StaticNode;

use super::eval;
use crate::error::EvalError;
use crate::filter::ast::{BoolOp, CompareOp, Filter};

pub fn eval_compare(
    left: &Filter,
    op: CompareOp,
    right: &Filter,
    value: &Value,
) -> Result<Vec<Value>, EvalError> {
    let left_results = eval(left, value)?;
    let right_results = eval(right, value)?;

    // Take first result from each side
    let left_val = left_results
        .into_iter()
        .next()
        .unwrap_or(Value::Static(StaticNode::Null));
    let right_val = right_results
        .into_iter()
        .next()
        .unwrap_or(Value::Static(StaticNode::Null));

    let result = match op {
        CompareOp::Eq => compare_values(&left_val, &right_val) == Some(std::cmp::Ordering::Equal),
        CompareOp::Ne => compare_values(&left_val, &right_val) != Some(std::cmp::Ordering::Equal),
        CompareOp::Lt => compare_values(&left_val, &right_val) == Some(std::cmp::Ordering::Less),
        CompareOp::Le => matches!(
            compare_values(&left_val, &right_val),
            Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
        ),
        CompareOp::Gt => compare_values(&left_val, &right_val) == Some(std::cmp::Ordering::Greater),
        CompareOp::Ge => matches!(
            compare_values(&left_val, &right_val),
            Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
        ),
    };

    Ok(vec![Value::Static(StaticNode::Bool(result))])
}

pub fn eval_bool_op(
    left: &Filter,
    op: BoolOp,
    right: &Filter,
    value: &Value,
) -> Result<Vec<Value>, EvalError> {
    // Evaluate left side first
    let left_results = eval(left, value)?;
    let left_truthy = left_results.iter().any(|v| {
        !matches!(
            v,
            Value::Static(StaticNode::Null) | Value::Static(StaticNode::Bool(false))
        )
    });

    // Short-circuit evaluation
    match op {
        BoolOp::And => {
            if !left_truthy {
                // Short-circuit: false and X = false
                Ok(vec![Value::Static(StaticNode::Bool(false))])
            } else {
                // Evaluate right side
                let right_results = eval(right, value)?;
                let right_truthy = right_results.iter().any(|v| {
                    !matches!(
                        v,
                        Value::Static(StaticNode::Null) | Value::Static(StaticNode::Bool(false))
                    )
                });
                Ok(vec![Value::Static(StaticNode::Bool(right_truthy))])
            }
        }
        BoolOp::Or => {
            if left_truthy {
                // Short-circuit: true or X = true
                Ok(vec![Value::Static(StaticNode::Bool(true))])
            } else {
                // Evaluate right side
                let right_results = eval(right, value)?;
                let right_truthy = right_results.iter().any(|v| {
                    !matches!(
                        v,
                        Value::Static(StaticNode::Null) | Value::Static(StaticNode::Bool(false))
                    )
                });
                Ok(vec![Value::Static(StaticNode::Bool(right_truthy))])
            }
        }
    }
}

/// Compare two JSON values following jq semantics.
/// Returns None if values are not comparable (different types for ordering).
pub fn compare_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
    use std::cmp::Ordering;

    match (a, b) {
        // Null
        (Value::Static(StaticNode::Null), Value::Static(StaticNode::Null)) => Some(Ordering::Equal),

        // Booleans: false < true
        (Value::Static(StaticNode::Bool(a)), Value::Static(StaticNode::Bool(b))) => Some(a.cmp(b)),

        // Numbers
        (Value::Static(StaticNode::I64(a)), Value::Static(StaticNode::I64(b))) => Some(a.cmp(b)),
        (Value::Static(StaticNode::U64(a)), Value::Static(StaticNode::U64(b))) => Some(a.cmp(b)),
        (Value::Static(StaticNode::F64(a)), Value::Static(StaticNode::F64(b))) => a.partial_cmp(b),
        (Value::Static(StaticNode::I64(a)), Value::Static(StaticNode::U64(b))) => {
            Some((*a as i128).cmp(&(*b as i128)))
        }
        (Value::Static(StaticNode::U64(a)), Value::Static(StaticNode::I64(b))) => {
            Some((*a as i128).cmp(&(*b as i128)))
        }
        (Value::Static(StaticNode::I64(a)), Value::Static(StaticNode::F64(b))) => {
            (*a as f64).partial_cmp(b)
        }
        (Value::Static(StaticNode::F64(a)), Value::Static(StaticNode::I64(b))) => {
            a.partial_cmp(&(*b as f64))
        }
        (Value::Static(StaticNode::U64(a)), Value::Static(StaticNode::F64(b))) => {
            (*a as f64).partial_cmp(b)
        }
        (Value::Static(StaticNode::F64(a)), Value::Static(StaticNode::U64(b))) => {
            a.partial_cmp(&(*b as f64))
        }

        // Strings: lexicographic
        (Value::String(a), Value::String(b)) => Some(a.cmp(b)),

        // Arrays: lexicographic comparison
        (Value::Array(a), Value::Array(b)) => {
            for (av, bv) in a.iter().zip(b.iter()) {
                match compare_values(av, bv) {
                    Some(Ordering::Equal) => continue,
                    other => return other,
                }
            }
            Some(a.len().cmp(&b.len()))
        }

        // Objects: compare by sorted keys, then values
        (Value::Object(a), Value::Object(b)) => {
            let mut a_keys: Vec<_> = a.keys().collect();
            let mut b_keys: Vec<_> = b.keys().collect();
            a_keys.sort();
            b_keys.sort();

            // First compare keys
            for (ak, bk) in a_keys.iter().zip(b_keys.iter()) {
                match ak.cmp(bk) {
                    Ordering::Equal => continue,
                    other => return Some(other),
                }
            }
            if a_keys.len() != b_keys.len() {
                return Some(a_keys.len().cmp(&b_keys.len()));
            }

            // Keys are equal, compare values
            for k in a_keys {
                match compare_values(&a[k], &b[k]) {
                    Some(Ordering::Equal) => continue,
                    other => return other,
                }
            }
            Some(Ordering::Equal)
        }

        // Different types: only equality is well-defined (and it's false)
        _ => None,
    }
}