use simd_json::OwnedValue as Value;
use simd_json::StaticNode;
use simd_json::prelude::*;
use super::{json_cmp, type_error};
use crate::error::EvalError;
use crate::utils::type_name;
pub fn eval_length(value: &Value) -> Result<Value, EvalError> {
let len = match value {
Value::Static(StaticNode::Null) => 0,
Value::String(s) => s.chars().count(),
Value::Array(arr) => arr.len(),
Value::Object(obj) => obj.len(),
Value::Static(
StaticNode::Bool(_) | StaticNode::I64(_) | StaticNode::U64(_) | StaticNode::F64(_),
) => {
return Err(type_error(format!("{} has no length", type_name(value))));
}
};
Ok(Value::Static(StaticNode::U64(len as u64)))
}
pub fn eval_min(value: &Value) -> Result<Value, EvalError> {
match value {
Value::Array(arr) if arr.is_empty() => Ok(Value::Static(StaticNode::Null)),
Value::Array(arr) => Ok(arr
.iter()
.min_by(|a, b| json_cmp(a, b))
.cloned()
.unwrap_or(Value::Static(StaticNode::Null))),
_ => Err(type_error(format!(
"{} cannot have its minimum taken, as it is not an array",
type_name(value)
))),
}
}
pub fn eval_max(value: &Value) -> Result<Value, EvalError> {
match value {
Value::Array(arr) if arr.is_empty() => Ok(Value::Static(StaticNode::Null)),
Value::Array(arr) => Ok(arr
.iter()
.max_by(|a, b| json_cmp(a, b))
.cloned()
.unwrap_or(Value::Static(StaticNode::Null))),
_ => Err(type_error(format!(
"{} cannot have its maximum taken, as it is not an array",
type_name(value)
))),
}
}
fn is_null(v: &Value) -> bool {
matches!(v, Value::Static(StaticNode::Null))
}
fn is_number(v: &Value) -> bool {
matches!(
v,
Value::Static(StaticNode::I64(_) | StaticNode::U64(_) | StaticNode::F64(_))
)
}
fn value_as_f64(v: &Value) -> Option<f64> {
match v {
Value::Static(StaticNode::I64(n)) => Some(*n as f64),
Value::Static(StaticNode::U64(n)) => Some(*n as f64),
Value::Static(StaticNode::F64(n)) => Some(*n),
_ => None,
}
}
pub fn eval_add(value: &Value) -> Result<Value, EvalError> {
match value {
Value::Array(arr) if arr.is_empty() => Ok(Value::Static(StaticNode::Null)),
Value::Array(arr) => {
let first_type = arr.iter().find(|v| !is_null(v));
Ok(match first_type {
Some(v) if is_number(v) => {
let sum: f64 = arr.iter().filter_map(value_as_f64).sum();
Value::Static(StaticNode::F64(sum))
}
Some(Value::String(_)) => {
let concat: String = arr.iter().filter_map(|v| v.as_str()).collect();
Value::String(concat)
}
Some(Value::Array(_)) => {
let concat: Vec<Value> = arr
.iter()
.filter_map(|v| v.as_array())
.flatten()
.cloned()
.collect();
Value::Array(Box::new(concat))
}
Some(Value::Object(_)) => {
let mut merged = simd_json::owned::Object::new();
for v in arr.iter() {
if let Value::Object(obj) = v {
for (k, val) in obj.iter() {
merged.insert(k.clone(), val.clone());
}
}
}
Value::Object(Box::new(merged))
}
_ => Value::Static(StaticNode::Null),
})
}
_ => Err(type_error(format!(
"{} cannot be added, 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_length_array() {
assert_eq!(
eval(&Builtin::Length, &json!([1, 2, 3])).unwrap(),
vec![json!(3)]
);
}
#[test]
fn test_length_string() {
assert_eq!(
eval(&Builtin::Length, &json!("hello")).unwrap(),
vec![json!(5)]
);
}
#[test]
fn test_length_string_unicode() {
assert_eq!(
eval(&Builtin::Length, &json!("日本語")).unwrap(),
vec![json!(3)]
);
}
#[test]
fn test_length_object() {
assert_eq!(
eval(&Builtin::Length, &json!({"a": 1, "b": 2})).unwrap(),
vec![json!(2)]
);
}
#[test]
fn test_length_null() {
assert_eq!(
eval(&Builtin::Length, &json!(null)).unwrap(),
vec![json!(0)]
);
}
#[test]
fn test_length_empty() {
assert_eq!(eval(&Builtin::Length, &json!([])).unwrap(), vec![json!(0)]);
assert_eq!(eval(&Builtin::Length, &json!("")).unwrap(), vec![json!(0)]);
assert_eq!(eval(&Builtin::Length, &json!({})).unwrap(), vec![json!(0)]);
}
#[test]
fn test_length_number_errors() {
let result = eval(&Builtin::Length, &json!(42));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("has no length"));
}
#[test]
fn test_length_bool_errors() {
let result = eval(&Builtin::Length, &json!(true));
assert!(result.is_err());
}
#[test]
fn test_min() {
assert_eq!(
eval(&Builtin::Min, &json!([3, 1, 2])).unwrap(),
vec![json!(1)]
);
}
#[test]
fn test_max() {
assert_eq!(
eval(&Builtin::Max, &json!([3, 1, 2])).unwrap(),
vec![json!(3)]
);
}
#[test]
fn test_min_empty() {
assert_eq!(eval(&Builtin::Min, &json!([])).unwrap(), vec![json!(null)]);
}
#[test]
fn test_max_empty() {
assert_eq!(eval(&Builtin::Max, &json!([])).unwrap(), vec![json!(null)]);
}
#[test]
fn test_min_strings() {
assert_eq!(
eval(&Builtin::Min, &json!(["banana", "apple", "cherry"])).unwrap(),
vec![json!("apple")]
);
}
#[test]
fn test_add_numbers() {
assert_eq!(
eval(&Builtin::Add, &json!([1, 2, 3])).unwrap(),
vec![json!(6.0)]
);
}
#[test]
fn test_add_strings() {
assert_eq!(
eval(&Builtin::Add, &json!(["a", "b", "c"])).unwrap(),
vec![json!("abc")]
);
}
#[test]
fn test_add_arrays() {
assert_eq!(
eval(&Builtin::Add, &json!([[1, 2], [3, 4]])).unwrap(),
vec![json!([1, 2, 3, 4])]
);
}
#[test]
fn test_add_empty() {
assert_eq!(eval(&Builtin::Add, &json!([])).unwrap(), vec![json!(null)]);
}
#[test]
fn test_add_non_array_errors() {
assert!(eval(&Builtin::Add, &json!("hello")).is_err());
}
#[test]
fn test_min_max_non_array_errors() {
assert!(eval(&Builtin::Min, &json!("hello")).is_err());
assert!(eval(&Builtin::Max, &json!(42)).is_err());
}
#[test]
fn test_add_objects() {
assert_eq!(
eval(&Builtin::Add, &json!([{"a": 1}, {"b": 2}])).unwrap(),
vec![json!({"a": 1, "b": 2})]
);
}
#[test]
fn test_add_objects_override() {
assert_eq!(
eval(&Builtin::Add, &json!([{"a": 1}, {"a": 2}])).unwrap(),
vec![json!({"a": 2})]
);
}
#[test]
fn test_add_objects_merge_multiple() {
assert_eq!(
eval(&Builtin::Add, &json!([{"a": 1}, {"b": 2}, {"c": 3}])).unwrap(),
vec![json!({"a": 1, "b": 2, "c": 3})]
);
}
}