use rayon::prelude::*;
use simd_json::OwnedValue as Value;
use simd_json::StaticNode;
use simd_json::prelude::*;
use super::ast::{BoolOp, CompareOp, Filter, FunctionCall, ObjectKey};
use super::builtins;
use crate::error::EvalError;
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::Static(StaticNode::Null)),
]),
_ => Ok(vec![Value::Static(StaticNode::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::Static(StaticNode::Null)),
])
}
_ => Ok(vec![Value::Static(StaticNode::Null)]),
},
Filter::Slice(start, end) => match value {
Value::Array(arr) => {
let len = arr.len() as i64;
let start_idx = match start {
Some(s) if *s >= 0 => (*s as usize).min(arr.len()),
Some(s) => (len + *s).max(0) as usize,
None => 0,
};
let end_idx = match end {
Some(e) if *e >= 0 => (*e as usize).min(arr.len()),
Some(e) => (len + *e).max(0) as usize,
None => arr.len(),
};
if start_idx >= end_idx {
Ok(vec![Value::Array(Box::default())])
} else {
Ok(vec![Value::Array(Box::new(
arr[start_idx..end_idx].to_vec(),
))])
}
}
Value::String(s) => {
let chars: Vec<char> = s.chars().collect();
let len = chars.len() as i64;
let start_idx = match start {
Some(st) if *st >= 0 => (*st as usize).min(chars.len()),
Some(st) => (len + *st).max(0) as usize,
None => 0,
};
let end_idx = match end {
Some(e) if *e >= 0 => (*e as usize).min(chars.len()),
Some(e) => (len + *e).max(0) as usize,
None => chars.len(),
};
if start_idx >= end_idx {
Ok(vec![Value::String(String::new())])
} else {
Ok(vec![Value::String(
chars[start_idx..end_idx].iter().collect(),
)])
}
}
_ => Ok(vec![Value::Static(StaticNode::Null)]),
},
Filter::Iterate => match value {
Value::Array(arr) if arr.len() >= PARALLEL_THRESHOLD => {
Ok(arr.par_iter().cloned().collect())
}
Value::Array(arr) => Ok(arr.to_vec()),
Value::Object(map) => Ok(map.values().cloned().collect()),
_ => Err(EvalError::CannotIterate {
value: value.clone(),
position: 0, }),
},
Filter::Optional(inner) => {
match eval(inner, value) {
Ok(results) => Ok(results),
Err(_) => Ok(vec![]),
}
}
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::Function(func) => eval_function(func, value),
Filter::Literal(lit) => Ok(vec![lit.clone()]),
Filter::Compare(left, op, right) => eval_compare(left, *op, right, value),
Filter::BoolOp(left, op, right) => eval_bool_op(left, *op, right, value),
Filter::Array(element_filters) => {
let mut array_elements = Vec::new();
for elem_filter in element_filters {
let results = eval(elem_filter, value)?;
array_elements.extend(results);
}
Ok(vec![Value::Array(Box::new(array_elements))])
}
Filter::Object(pairs) => {
let mut obj = simd_json::owned::Object::new();
for (key, value_filter) in pairs {
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)?;
let val = results
.into_iter()
.next()
.unwrap_or(Value::Static(StaticNode::Null));
obj.insert(key_str, val);
}
Ok(vec![Value::Object(Box::new(obj))])
}
Filter::IfThenElse {
condition,
then_branch,
else_branch,
} => {
let cond_results = eval(condition, value)?;
let is_truthy = cond_results.iter().any(|v| {
!matches!(
v,
Value::Static(StaticNode::Null) | Value::Static(StaticNode::Bool(false))
)
});
if is_truthy {
eval(then_branch, value)
} else {
eval(else_branch, value)
}
}
Filter::Alternative(left, right) => {
let left_results = eval(left, value)?;
let has_value = left_results.iter().any(|v| {
!matches!(
v,
Value::Static(StaticNode::Null) | Value::Static(StaticNode::Bool(false))
)
});
if has_value {
let filtered: Vec<Value> = left_results
.into_iter()
.filter(|v| {
!matches!(
v,
Value::Static(StaticNode::Null)
| Value::Static(StaticNode::Bool(false))
)
})
.collect();
if filtered.is_empty() {
eval(right, value)
} else {
Ok(filtered)
}
} else {
eval(right, value)
}
}
}
}
fn type_name(v: &Value) -> &'static str {
match v {
Value::Static(StaticNode::Null) => "null",
Value::Static(StaticNode::Bool(_)) => "boolean",
Value::Static(StaticNode::I64(_) | StaticNode::U64(_) | StaticNode::F64(_)) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
fn eval_function(func: &FunctionCall, value: &Value) -> Result<Vec<Value>, EvalError> {
match func {
FunctionCall::Map(inner) => eval_map(inner, value),
FunctionCall::Select(inner) => eval_select(inner, value),
FunctionCall::SortBy(inner) => eval_sort_by(inner, value),
FunctionCall::GroupBy(inner) => eval_group_by(inner, value),
FunctionCall::UniqueBy(inner) => eval_unique_by(inner, value),
FunctionCall::MinBy(inner) => eval_min_by(inner, value),
FunctionCall::MaxBy(inner) => eval_max_by(inner, value),
FunctionCall::Has(key) => eval_has(key, value),
FunctionCall::Split(delim) => eval_split(delim, value),
FunctionCall::Join(sep) => eval_join(sep, value),
FunctionCall::StartsWith(prefix) => eval_startswith(prefix, value),
FunctionCall::EndsWith(suffix) => eval_endswith(suffix, value),
FunctionCall::Contains(inner) => eval_contains(inner, value),
FunctionCall::WithEntries(inner) => eval_with_entries(inner, value),
}
}
fn eval_has(key: &str, value: &Value) -> Result<Vec<Value>, EvalError> {
match value {
Value::Object(map) => Ok(vec![Value::Static(StaticNode::Bool(map.contains_key(key)))]),
Value::Array(arr) => {
if let Ok(idx) = key.parse::<usize>() {
Ok(vec![Value::Static(StaticNode::Bool(idx < arr.len()))])
} else {
Ok(vec![Value::Static(StaticNode::Bool(false))])
}
}
_ => Ok(vec![Value::Static(StaticNode::Bool(false))]),
}
}
fn eval_split(delim: &str, value: &Value) -> Result<Vec<Value>, EvalError> {
match value {
Value::String(s) => {
if s.is_empty() {
return Ok(vec![Value::Array(Box::default())]);
}
if delim.is_empty() {
let parts: Vec<Value> = s.chars().map(|c| Value::String(c.to_string())).collect();
return Ok(vec![Value::Array(Box::new(parts))]);
}
let parts: Vec<Value> = s
.split(delim)
.map(|p| Value::String(p.to_string()))
.collect();
Ok(vec![Value::Array(Box::new(parts))])
}
_ => Err(EvalError::TypeError {
message: format!("split requires string, got {}", type_name(value)),
position: 0,
}),
}
}
fn eval_join(sep: &str, value: &Value) -> Result<Vec<Value>, EvalError> {
match value {
Value::Array(arr) => {
let mut strings = Vec::new();
for v in arr.iter() {
match v {
Value::String(s) => strings.push(s.clone()),
Value::Static(StaticNode::Null) => strings.push(String::new()), _ => {
return Err(EvalError::TypeError {
message: format!(
"join requires array of strings, got {} in array",
type_name(v)
),
position: 0,
});
}
}
}
Ok(vec![Value::String(strings.join(sep))])
}
_ => Err(EvalError::TypeError {
message: format!("join requires array, got {}", type_name(value)),
position: 0,
}),
}
}
fn eval_startswith(prefix: &str, value: &Value) -> Result<Vec<Value>, EvalError> {
match value {
Value::String(s) => Ok(vec![Value::Static(StaticNode::Bool(s.starts_with(prefix)))]),
_ => Err(EvalError::TypeError {
message: format!("startswith requires string, got {}", type_name(value)),
position: 0,
}),
}
}
fn eval_endswith(suffix: &str, value: &Value) -> Result<Vec<Value>, EvalError> {
match value {
Value::String(s) => Ok(vec![Value::Static(StaticNode::Bool(s.ends_with(suffix)))]),
_ => Err(EvalError::TypeError {
message: format!("endswith requires string, got {}", type_name(value)),
position: 0,
}),
}
}
fn eval_contains(inner: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
let needle_results = eval(inner, value)?;
let needle = needle_results
.into_iter()
.next()
.unwrap_or(Value::Static(StaticNode::Null));
Ok(vec![Value::Static(StaticNode::Bool(value_contains(
value, &needle,
)))])
}
fn value_contains(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::String(haystack), Value::String(needle)) => haystack.contains(needle.as_str()),
(Value::Array(arr), Value::Array(needle_arr)) => {
needle_arr
.iter()
.all(|needle_elem| arr.iter().any(|elem| value_contains(elem, needle_elem)))
}
(Value::Object(obj), Value::Object(needle_obj)) => needle_obj
.iter()
.all(|(k, v)| obj.get(k).map(|ov| value_contains(ov, v)).unwrap_or(false)),
_ => compare_values(a, b) == Some(std::cmp::Ordering::Equal),
}
}
fn eval_with_entries(inner: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
match value {
Value::Object(obj) => {
let entries: Vec<Value> = obj
.iter()
.map(|(k, v)| {
let mut entry = simd_json::owned::Object::new();
entry.insert("key".to_string(), Value::String(k.clone()));
entry.insert("value".to_string(), v.clone());
Value::Object(Box::new(entry))
})
.collect();
let mut transformed = Vec::new();
for entry in entries {
transformed.extend(eval(inner, &entry)?);
}
let mut result = simd_json::owned::Object::new();
for entry in transformed {
let key = entry
.get("key")
.or_else(|| entry.get("k"))
.or_else(|| entry.get("name"))
.and_then(|k| k.as_str())
.ok_or_else(|| EvalError::TypeError {
message: "Cannot use null as object key".to_string(),
position: 0,
})?;
let val = entry
.get("value")
.or_else(|| entry.get("v"))
.cloned()
.unwrap_or(Value::Static(StaticNode::Null));
result.insert(key.to_string(), val);
}
Ok(vec![Value::Object(Box::new(result))])
}
_ => Err(EvalError::TypeError {
message: format!("with_entries requires object, got {}", type_name(value)),
position: 0,
}),
}
}
fn eval_map(inner: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
match value {
Value::Array(arr) => {
let mut results = Vec::new();
for elem in arr.iter() {
results.extend(eval(inner, elem)?);
}
Ok(vec![Value::Array(Box::new(results))])
}
_ => Err(EvalError::TypeError {
message: format!("map requires array, got {}", type_name(value)),
position: 0,
}),
}
}
fn eval_select(inner: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
let results = eval(inner, value)?;
let is_truthy = results.iter().any(|v| {
!matches!(
v,
Value::Static(StaticNode::Null) | Value::Static(StaticNode::Bool(false))
)
});
if is_truthy {
Ok(vec![value.clone()])
} else {
Ok(vec![]) }
}
fn eval_sort_by(inner: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
match value {
Value::Array(arr) => {
let mut keyed: Vec<(Value, Value)> = arr
.iter()
.map(|elem| {
let key = eval(inner, elem)?
.into_iter()
.next()
.unwrap_or(Value::Static(StaticNode::Null));
Ok((key, elem.clone()))
})
.collect::<Result<Vec<_>, EvalError>>()?;
keyed.sort_by(|(a, _), (b, _)| {
compare_values(a, b).unwrap_or(std::cmp::Ordering::Equal)
});
let sorted: Vec<Value> = keyed.into_iter().map(|(_, v)| v).collect();
Ok(vec![Value::Array(Box::new(sorted))])
}
_ => Err(EvalError::TypeError {
message: format!("sort_by requires array, got {}", type_name(value)),
position: 0,
}),
}
}
fn eval_group_by(inner: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
match value {
Value::Array(arr) => {
let mut groups: Vec<(Value, Vec<Value>)> = vec![];
for elem in arr.iter() {
let key = eval(inner, elem)?
.into_iter()
.next()
.unwrap_or(Value::Static(StaticNode::Null));
if let Some((_, group)) = groups
.iter_mut()
.find(|(k, _)| compare_values(k, &key) == Some(std::cmp::Ordering::Equal))
{
group.push(elem.clone());
} else {
groups.push((key, vec![elem.clone()]));
}
}
groups.sort_by(|(a, _), (b, _)| {
compare_values(a, b).unwrap_or(std::cmp::Ordering::Equal)
});
let result: Vec<Value> = groups
.into_iter()
.map(|(_, group)| Value::Array(Box::new(group)))
.collect();
Ok(vec![Value::Array(Box::new(result))])
}
_ => Err(EvalError::TypeError {
message: format!("group_by requires array, got {}", type_name(value)),
position: 0,
}),
}
}
fn eval_unique_by(inner: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
match value {
Value::Array(arr) => {
let mut seen: Vec<Value> = vec![];
let mut result: Vec<Value> = vec![];
for elem in arr.iter() {
let key = eval(inner, elem)?
.into_iter()
.next()
.unwrap_or(Value::Static(StaticNode::Null));
let is_new = !seen
.iter()
.any(|k| compare_values(k, &key) == Some(std::cmp::Ordering::Equal));
if is_new {
seen.push(key);
result.push(elem.clone());
}
}
Ok(vec![Value::Array(Box::new(result))])
}
_ => Err(EvalError::TypeError {
message: format!("unique_by requires array, got {}", type_name(value)),
position: 0,
}),
}
}
fn eval_min_by(inner: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
eval_extremum_by(inner, value, true)
}
fn eval_max_by(inner: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
eval_extremum_by(inner, value, false)
}
fn eval_extremum_by(inner: &Filter, value: &Value, is_min: bool) -> Result<Vec<Value>, EvalError> {
let func_name = if is_min { "min_by" } else { "max_by" };
match value {
Value::Array(arr) if arr.is_empty() => Ok(vec![Value::Static(StaticNode::Null)]),
Value::Array(arr) => {
let mut best_elem = &arr[0];
let mut best_key = eval(inner, best_elem)?
.into_iter()
.next()
.unwrap_or(Value::Static(StaticNode::Null));
for elem in arr.iter().skip(1) {
let key = eval(inner, elem)?
.into_iter()
.next()
.unwrap_or(Value::Static(StaticNode::Null));
let cmp = compare_values(&key, &best_key);
let should_replace = if is_min {
cmp == Some(std::cmp::Ordering::Less)
} else {
cmp == Some(std::cmp::Ordering::Greater)
};
if should_replace {
best_elem = elem;
best_key = key;
}
}
Ok(vec![best_elem.clone()])
}
_ => Err(EvalError::TypeError {
message: format!("{} requires array, got {}", func_name, type_name(value)),
position: 0,
}),
}
}
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)?;
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))])
}
fn eval_bool_op(
left: &Filter,
op: BoolOp,
right: &Filter,
value: &Value,
) -> Result<Vec<Value>, EvalError> {
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))
)
});
match op {
BoolOp::And => {
if !left_truthy {
Ok(vec![Value::Static(StaticNode::Bool(false))])
} else {
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 {
Ok(vec![Value::Static(StaticNode::Bool(true))])
} else {
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))])
}
}
}
}
fn compare_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
use std::cmp::Ordering;
match (a, b) {
(Value::Static(StaticNode::Null), Value::Static(StaticNode::Null)) => Some(Ordering::Equal),
(Value::Static(StaticNode::Bool(a)), Value::Static(StaticNode::Bool(b))) => Some(a.cmp(b)),
(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))
}
(Value::String(a), Value::String(b)) => Some(a.cmp(b)),
(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()))
}
(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();
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()));
}
for k in a_keys {
match compare_values(&a[k], &b[k]) {
Some(Ordering::Equal) => continue,
other => return other,
}
}
Some(Ordering::Equal)
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use simd_json::json;
#[test]
fn test_identity() {
let value = json!({"name": "alice"});
let result = eval(&Filter::Identity, &value).unwrap();
assert_eq!(result, vec![value]);
}
#[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::Static(StaticNode::Null)]);
}
#[test]
fn test_field_on_non_object() {
let result = eval(&Filter::Field("foo".to_string()), &json!("string")).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
let result = eval(&Filter::Field("foo".to_string()), &json!(123)).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
let result = eval(&Filter::Field("foo".to_string()), &json!(null)).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
let result = eval(&Filter::Field("foo".to_string()), &json!([1, 2, 3])).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::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::Static(StaticNode::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::Static(StaticNode::Null)]);
}
#[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::Static(StaticNode::Null)]
);
assert_eq!(
eval(&Filter::Index(-10), &value).unwrap(),
vec![Value::Static(StaticNode::Null)]
);
}
#[test]
fn test_index_on_non_array() {
assert_eq!(
eval(&Filter::Index(0), &json!({"a": 1})).unwrap(),
vec![Value::Static(StaticNode::Null)]
);
assert_eq!(
eval(&Filter::Index(0), &json!("string")).unwrap(),
vec![Value::Static(StaticNode::Null)]
);
assert_eq!(
eval(&Filter::Index(0), &json!(123)).unwrap(),
vec![Value::Static(StaticNode::Null)]
);
assert_eq!(
eval(&Filter::Index(0), &json!(null)).unwrap(),
vec![Value::Static(StaticNode::Null)]
);
}
#[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();
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 { .. })
));
}
#[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)]);
}
#[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")]
);
}
#[test]
fn test_pipe_explicit() {
let value = json!({"users": [{"name": "alice"}, {"name": "bob"}]});
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")]
);
}
#[test]
fn test_optional_iterate_on_non_iterable() {
let filter = Filter::Optional(Box::new(Filter::Iterate));
let result = eval(&filter, &json!("not an array")).unwrap();
assert_eq!(result, Vec::<Value>::new());
}
#[test]
fn test_optional_iterate_on_array() {
let filter = Filter::Optional(Box::new(Filter::Iterate));
let result = eval(&filter, &json!([1, 2, 3])).unwrap();
assert_eq!(result, vec![json!(1), json!(2), json!(3)]);
}
#[test]
fn test_optional_field_succeeds() {
let filter = Filter::Optional(Box::new(Filter::Field("foo".to_string())));
let result = eval(&filter, &json!({"foo": "bar"})).unwrap();
assert_eq!(result, vec![json!("bar")]);
}
#[test]
fn test_slice_both_indices() {
let filter = Filter::Slice(Some(1), Some(3));
let result = eval(&filter, &json!([0, 1, 2, 3, 4])).unwrap();
assert_eq!(result, vec![json!([1, 2])]);
}
#[test]
fn test_slice_start_only() {
let filter = Filter::Slice(Some(2), None);
let result = eval(&filter, &json!([0, 1, 2, 3, 4])).unwrap();
assert_eq!(result, vec![json!([2, 3, 4])]);
}
#[test]
fn test_slice_end_only() {
let filter = Filter::Slice(None, Some(3));
let result = eval(&filter, &json!([0, 1, 2, 3, 4])).unwrap();
assert_eq!(result, vec![json!([0, 1, 2])]);
}
#[test]
fn test_slice_negative_end() {
let filter = Filter::Slice(None, Some(-1));
let result = eval(&filter, &json!([0, 1, 2, 3, 4])).unwrap();
assert_eq!(result, vec![json!([0, 1, 2, 3])]);
}
#[test]
fn test_slice_negative_start() {
let filter = Filter::Slice(Some(-2), None);
let result = eval(&filter, &json!([0, 1, 2, 3, 4])).unwrap();
assert_eq!(result, vec![json!([3, 4])]);
}
#[test]
fn test_slice_both_negative() {
let filter = Filter::Slice(Some(-3), Some(-1));
let result = eval(&filter, &json!([0, 1, 2, 3, 4])).unwrap();
assert_eq!(result, vec![json!([2, 3])]);
}
#[test]
fn test_slice_empty_result() {
let filter = Filter::Slice(Some(3), Some(1));
let result = eval(&filter, &json!([0, 1, 2, 3, 4])).unwrap();
assert_eq!(result, vec![json!([])]);
}
#[test]
fn test_slice_string() {
let filter = Filter::Slice(Some(1), Some(4));
let result = eval(&filter, &json!("hello")).unwrap();
assert_eq!(result, vec![json!("ell")]);
}
#[test]
fn test_slice_on_non_array() {
let filter = Filter::Slice(Some(0), Some(2));
let result = eval(&filter, &json!(123)).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
}
#[test]
fn test_map_field_access() {
let filter = Filter::Function(FunctionCall::Map(Box::new(Filter::Field("a".to_string()))));
let result = eval(&filter, &json!([{"a": 1}, {"a": 2}])).unwrap();
assert_eq!(result, vec![json!([1, 2])]);
}
#[test]
fn test_map_identity() {
let filter = Filter::Function(FunctionCall::Map(Box::new(Filter::Identity)));
let result = eval(&filter, &json!([1, 2, 3])).unwrap();
assert_eq!(result, vec![json!([1, 2, 3])]);
}
#[test]
fn test_map_on_empty_array() {
let filter = Filter::Function(FunctionCall::Map(Box::new(Filter::Field("x".to_string()))));
let result = eval(&filter, &json!([])).unwrap();
assert_eq!(result, vec![json!([])]);
}
#[test]
fn test_map_on_non_array_fails() {
let filter = Filter::Function(FunctionCall::Map(Box::new(Filter::Identity)));
let result = eval(&filter, &json!({"a": 1}));
assert!(matches!(result, Err(EvalError::TypeError { .. })));
}
#[test]
fn test_map_with_iterate() {
let filter = Filter::Function(FunctionCall::Map(Box::new(Filter::Iterate)));
let result = eval(&filter, &json!([[1, 2], [3, 4]])).unwrap();
assert_eq!(result, vec![json!([1, 2, 3, 4])]);
}
#[test]
fn test_select_true() {
let filter = Filter::Function(FunctionCall::Select(Box::new(Filter::Field(
"active".to_string(),
))));
let value = json!({"active": true, "name": "test"});
let result = eval(&filter, &value).unwrap();
assert_eq!(result, vec![value]);
}
#[test]
fn test_select_false() {
let filter = Filter::Function(FunctionCall::Select(Box::new(Filter::Field(
"active".to_string(),
))));
let result = eval(&filter, &json!({"active": false})).unwrap();
assert_eq!(result, Vec::<Value>::new());
}
#[test]
fn test_select_null() {
let filter = Filter::Function(FunctionCall::Select(Box::new(Filter::Field(
"active".to_string(),
))));
let result = eval(&filter, &json!({"active": null})).unwrap();
assert_eq!(result, Vec::<Value>::new());
}
#[test]
fn test_select_truthy_number() {
let filter = Filter::Function(FunctionCall::Select(Box::new(Filter::Identity)));
let result = eval(&filter, &json!(0)).unwrap();
assert_eq!(result, vec![json!(0)]);
}
#[test]
fn test_select_truthy_empty_string() {
let filter = Filter::Function(FunctionCall::Select(Box::new(Filter::Identity)));
let result = eval(&filter, &json!("")).unwrap();
assert_eq!(result, vec![json!("")]);
}
#[test]
fn test_select_truthy_empty_array() {
let filter = Filter::Function(FunctionCall::Select(Box::new(Filter::Identity)));
let result = eval(&filter, &json!([])).unwrap();
assert_eq!(result, vec![json!([])]);
}
#[test]
fn test_select_in_pipe_with_iterate() {
let filter = Filter::Pipe(
Box::new(Filter::Iterate),
Box::new(Filter::Function(FunctionCall::Select(Box::new(
Filter::Field("x".to_string()),
)))),
);
let result = eval(&filter, &json!([{"x": true}, {"x": false}, {"x": true}])).unwrap();
assert_eq!(result, vec![json!({"x": true}), json!({"x": true})]);
}
#[test]
fn test_sort_by_field() {
let filter = Filter::Function(FunctionCall::SortBy(Box::new(Filter::Field(
"age".to_string(),
))));
let input = json!([{"age": 30, "name": "alice"}, {"age": 20, "name": "bob"}, {"age": 25, "name": "charlie"}]);
let result = eval(&filter, &input).unwrap();
assert_eq!(
result,
vec![json!([
{"age": 20, "name": "bob"},
{"age": 25, "name": "charlie"},
{"age": 30, "name": "alice"}
])]
);
}
#[test]
fn test_sort_by_empty_array() {
let filter = Filter::Function(FunctionCall::SortBy(Box::new(Filter::Field(
"x".to_string(),
))));
let result = eval(&filter, &json!([])).unwrap();
assert_eq!(result, vec![json!([])]);
}
#[test]
fn test_sort_by_on_non_array_fails() {
let filter = Filter::Function(FunctionCall::SortBy(Box::new(Filter::Identity)));
let result = eval(&filter, &json!({"a": 1}));
assert!(matches!(result, Err(EvalError::TypeError { .. })));
}
#[test]
fn test_group_by_field() {
let filter = Filter::Function(FunctionCall::GroupBy(Box::new(Filter::Field(
"type".to_string(),
))));
let input = json!([
{"type": "a", "v": 1},
{"type": "b", "v": 2},
{"type": "a", "v": 3}
]);
let result = eval(&filter, &input).unwrap();
assert_eq!(
result,
vec![json!([
[{"type": "a", "v": 1}, {"type": "a", "v": 3}],
[{"type": "b", "v": 2}]
])]
);
}
#[test]
fn test_group_by_empty_array() {
let filter = Filter::Function(FunctionCall::GroupBy(Box::new(Filter::Field(
"x".to_string(),
))));
let result = eval(&filter, &json!([])).unwrap();
assert_eq!(result, vec![json!([])]);
}
#[test]
fn test_unique_by_field() {
let filter = Filter::Function(FunctionCall::UniqueBy(Box::new(Filter::Field(
"id".to_string(),
))));
let input = json!([
{"id": 1, "v": "a"},
{"id": 2, "v": "b"},
{"id": 1, "v": "c"}
]);
let result = eval(&filter, &input).unwrap();
assert_eq!(
result,
vec![json!([{"id": 1, "v": "a"}, {"id": 2, "v": "b"}])]
);
}
#[test]
fn test_unique_by_preserves_first() {
let filter = Filter::Function(FunctionCall::UniqueBy(Box::new(Filter::Field(
"k".to_string(),
))));
let input = json!([{"k": "x", "n": 1}, {"k": "x", "n": 2}, {"k": "x", "n": 3}]);
let result = eval(&filter, &input).unwrap();
assert_eq!(result, vec![json!([{"k": "x", "n": 1}])]);
}
#[test]
fn test_min_by_field() {
let filter = Filter::Function(FunctionCall::MinBy(Box::new(Filter::Field(
"age".to_string(),
))));
let input = json!([{"age": 30}, {"age": 20}, {"age": 25}]);
let result = eval(&filter, &input).unwrap();
assert_eq!(result, vec![json!({"age": 20})]);
}
#[test]
fn test_min_by_empty_array() {
let filter = Filter::Function(FunctionCall::MinBy(Box::new(Filter::Field(
"x".to_string(),
))));
let result = eval(&filter, &json!([])).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
}
#[test]
fn test_max_by_field() {
let filter = Filter::Function(FunctionCall::MaxBy(Box::new(Filter::Field(
"score".to_string(),
))));
let input = json!([{"score": 10}, {"score": 50}, {"score": 30}]);
let result = eval(&filter, &input).unwrap();
assert_eq!(result, vec![json!({"score": 50})]);
}
#[test]
fn test_max_by_empty_array() {
let filter = Filter::Function(FunctionCall::MaxBy(Box::new(Filter::Field(
"x".to_string(),
))));
let result = eval(&filter, &json!([])).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
}
#[test]
fn test_max_by_single_element() {
let filter = Filter::Function(FunctionCall::MaxBy(Box::new(Filter::Field(
"x".to_string(),
))));
let result = eval(&filter, &json!([{"x": 42}])).unwrap();
assert_eq!(result, vec![json!({"x": 42})]);
}
#[test]
fn test_if_then_else_true_condition() {
let filter = Filter::IfThenElse {
condition: Box::new(Filter::Literal(json!(true))),
then_branch: Box::new(Filter::Literal(json!("yes"))),
else_branch: Box::new(Filter::Literal(json!("no"))),
};
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!("yes")]);
}
#[test]
fn test_if_then_else_false_condition() {
let filter = Filter::IfThenElse {
condition: Box::new(Filter::Literal(json!(false))),
then_branch: Box::new(Filter::Literal(json!("yes"))),
else_branch: Box::new(Filter::Literal(json!("no"))),
};
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!("no")]);
}
#[test]
fn test_if_then_else_null_is_falsy() {
let filter = Filter::IfThenElse {
condition: Box::new(Filter::Literal(json!(null))),
then_branch: Box::new(Filter::Literal(json!("yes"))),
else_branch: Box::new(Filter::Literal(json!("no"))),
};
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!("no")]);
}
#[test]
fn test_if_then_else_zero_is_truthy() {
let filter = Filter::IfThenElse {
condition: Box::new(Filter::Literal(json!(0))),
then_branch: Box::new(Filter::Literal(json!("yes"))),
else_branch: Box::new(Filter::Literal(json!("no"))),
};
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!("yes")]);
}
#[test]
fn test_alternative_left_null() {
let filter = Filter::Alternative(
Box::new(Filter::Literal(json!(null))),
Box::new(Filter::Literal(json!("default"))),
);
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!("default")]);
}
#[test]
fn test_alternative_left_false() {
let filter = Filter::Alternative(
Box::new(Filter::Literal(json!(false))),
Box::new(Filter::Literal(json!("default"))),
);
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!("default")]);
}
#[test]
fn test_alternative_left_truthy() {
let filter = Filter::Alternative(
Box::new(Filter::Literal(json!("value"))),
Box::new(Filter::Literal(json!("default"))),
);
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!("value")]);
}
#[test]
fn test_alternative_left_zero_truthy() {
let filter = Filter::Alternative(
Box::new(Filter::Literal(json!(0))),
Box::new(Filter::Literal(json!("default"))),
);
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!(0)]);
}
#[test]
fn test_alternative_with_field() {
let filter = Filter::Alternative(
Box::new(Filter::Field("name".to_string())),
Box::new(Filter::Literal(json!("unknown"))),
);
assert_eq!(eval(&filter, &json!({})).unwrap(), vec![json!("unknown")]);
assert_eq!(
eval(&filter, &json!({"name": "alice"})).unwrap(),
vec![json!("alice")]
);
}
#[test]
fn test_has_present_key() {
let filter = Filter::Function(FunctionCall::Has("name".to_string()));
assert_eq!(
eval(&filter, &json!({"name": "alice"})).unwrap(),
vec![json!(true)]
);
}
#[test]
fn test_has_missing_key() {
let filter = Filter::Function(FunctionCall::Has("name".to_string()));
assert_eq!(
eval(&filter, &json!({"age": 30})).unwrap(),
vec![json!(false)]
);
}
#[test]
fn test_has_null_value() {
let filter = Filter::Function(FunctionCall::Has("name".to_string()));
assert_eq!(
eval(&filter, &json!({"name": null})).unwrap(),
vec![json!(true)]
);
}
#[test]
fn test_has_on_non_object() {
let filter = Filter::Function(FunctionCall::Has("x".to_string()));
assert_eq!(eval(&filter, &json!("string")).unwrap(), vec![json!(false)]);
assert_eq!(eval(&filter, &json!(123)).unwrap(), vec![json!(false)]);
}
#[test]
fn test_split_basic() {
let filter = Filter::Function(FunctionCall::Split("/".to_string()));
assert_eq!(
eval(&filter, &json!("a/b/c")).unwrap(),
vec![json!(["a", "b", "c"])]
);
}
#[test]
fn test_split_empty_delim() {
let filter = Filter::Function(FunctionCall::Split("".to_string()));
assert_eq!(
eval(&filter, &json!("hello")).unwrap(),
vec![json!(["h", "e", "l", "l", "o"])]
);
}
#[test]
fn test_split_empty_string() {
let filter = Filter::Function(FunctionCall::Split("/".to_string()));
assert_eq!(eval(&filter, &json!("")).unwrap(), vec![json!([])]);
}
#[test]
fn test_join_basic() {
let filter = Filter::Function(FunctionCall::Join("-".to_string()));
assert_eq!(
eval(&filter, &json!(["a", "b", "c"])).unwrap(),
vec![json!("a-b-c")]
);
}
#[test]
fn test_join_empty_array() {
let filter = Filter::Function(FunctionCall::Join("-".to_string()));
assert_eq!(eval(&filter, &json!([])).unwrap(), vec![json!("")]);
}
#[test]
fn test_join_with_null() {
let filter = Filter::Function(FunctionCall::Join("-".to_string()));
assert_eq!(
eval(&filter, &json!(["a", null, "b"])).unwrap(),
vec![json!("a--b")]
);
}
#[test]
fn test_startswith_true() {
let filter = Filter::Function(FunctionCall::StartsWith("hello".to_string()));
assert_eq!(
eval(&filter, &json!("hello world")).unwrap(),
vec![json!(true)]
);
}
#[test]
fn test_startswith_false() {
let filter = Filter::Function(FunctionCall::StartsWith("world".to_string()));
assert_eq!(
eval(&filter, &json!("hello world")).unwrap(),
vec![json!(false)]
);
}
#[test]
fn test_endswith_true() {
let filter = Filter::Function(FunctionCall::EndsWith(".json".to_string()));
assert_eq!(
eval(&filter, &json!("file.json")).unwrap(),
vec![json!(true)]
);
}
#[test]
fn test_endswith_false() {
let filter = Filter::Function(FunctionCall::EndsWith(".json".to_string()));
assert_eq!(
eval(&filter, &json!("file.txt")).unwrap(),
vec![json!(false)]
);
}
#[test]
fn test_contains_string() {
let filter = Filter::Function(FunctionCall::Contains(Box::new(Filter::Literal(json!(
"wor"
)))));
assert_eq!(
eval(&filter, &json!("hello world")).unwrap(),
vec![json!(true)]
);
}
#[test]
fn test_contains_string_false() {
let filter = Filter::Function(FunctionCall::Contains(Box::new(Filter::Literal(json!(
"xyz"
)))));
assert_eq!(eval(&filter, &json!("hello")).unwrap(), vec![json!(false)]);
}
#[test]
fn test_contains_array_single() {
let filter = Filter::Function(FunctionCall::Contains(Box::new(Filter::Array(vec![
Filter::Literal(json!(2)),
]))));
assert_eq!(eval(&filter, &json!([1, 2, 3])).unwrap(), vec![json!(true)]);
}
#[test]
fn test_contains_array_multi() {
let filter = Filter::Function(FunctionCall::Contains(Box::new(Filter::Array(vec![
Filter::Literal(json!(2)),
Filter::Literal(json!(3)),
]))));
assert_eq!(eval(&filter, &json!([1, 2, 3])).unwrap(), vec![json!(true)]);
}
#[test]
fn test_contains_array_false() {
let filter = Filter::Function(FunctionCall::Contains(Box::new(Filter::Array(vec![
Filter::Literal(json!(4)),
]))));
assert_eq!(
eval(&filter, &json!([1, 2, 3])).unwrap(),
vec![json!(false)]
);
}
#[test]
fn test_contains_object() {
let filter = Filter::Function(FunctionCall::Contains(Box::new(Filter::Object(vec![(
ObjectKey::Static("a".to_string()),
Filter::Literal(json!(1)),
)]))));
assert_eq!(
eval(&filter, &json!({"a": 1, "b": 2})).unwrap(),
vec![json!(true)]
);
}
#[test]
fn test_contains_object_false() {
let filter = Filter::Function(FunctionCall::Contains(Box::new(Filter::Object(vec![(
ObjectKey::Static("c".to_string()),
Filter::Literal(json!(3)),
)]))));
assert_eq!(
eval(&filter, &json!({"a": 1, "b": 2})).unwrap(),
vec![json!(false)]
);
}
#[test]
fn test_with_entries_identity() {
let filter = Filter::Function(FunctionCall::WithEntries(Box::new(Filter::Identity)));
let result = eval(&filter, &json!({"a": 1, "b": 2})).unwrap();
assert_eq!(result, vec![json!({"a": 1, "b": 2})]);
}
#[test]
fn test_with_entries_select() {
let filter = Filter::Function(FunctionCall::WithEntries(Box::new(Filter::Function(
FunctionCall::Select(Box::new(Filter::Compare(
Box::new(Filter::Field("value".to_string())),
CompareOp::Gt,
Box::new(Filter::Literal(json!(1))),
))),
))));
let result = eval(&filter, &json!({"a": 1, "b": 2, "c": 3})).unwrap();
assert_eq!(result, vec![json!({"b": 2, "c": 3})]);
}
#[test]
fn test_with_entries_empty() {
let filter = Filter::Function(FunctionCall::WithEntries(Box::new(Filter::Identity)));
let result = eval(&filter, &json!({})).unwrap();
assert_eq!(result, vec![json!({})]);
}
#[test]
fn test_with_entries_non_object_errors() {
let filter = Filter::Function(FunctionCall::WithEntries(Box::new(Filter::Identity)));
assert!(eval(&filter, &json!([1, 2, 3])).is_err());
}
}