use jsonata_core::{
evaluator::{Context, Evaluator, EvaluatorOptions},
parser::parse,
value::JValue,
};
use serde_json::json;
#[test]
fn test_simple_field_access() {
let data: JValue = json!({
"name": "Alice",
"age": 30
})
.into();
let ast = parse("name").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!("Alice")));
}
#[test]
fn test_nested_field_access() {
let data: JValue = json!({
"user": {
"profile": {
"name": "Bob"
}
}
})
.into();
let ast = parse("user.profile.name").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!("Bob")));
}
#[test]
fn test_arithmetic_expression() {
let data: JValue = json!({
"price": 100,
"quantity": 5
})
.into();
let ast = parse("price * quantity").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(500.0)));
let ast = parse("(price + 10) * quantity").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(550.0)));
}
#[test]
fn test_comparison_expression() {
let data: JValue = json!({
"age": 25,
"threshold": 18
})
.into();
let ast = parse("age > threshold").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::Bool(true));
}
#[test]
fn test_logical_expression() {
let data: JValue = json!({
"age": 25,
"has_license": true
})
.into();
let ast = parse("age >= 18 and has_license").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::Bool(true));
}
#[test]
fn test_string_concatenation() {
let data: JValue = json!({
"first": "Hello",
"second": "World"
})
.into();
let ast = parse(r#"first & " " & second"#).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!("Hello World")));
}
#[test]
fn test_function_call() {
let data: JValue = json!({
"name": "alice"
})
.into();
let ast = parse("$uppercase(name)").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!("ALICE")));
}
#[test]
fn test_nested_function_calls() {
let data: JValue = json!({
"text": "HELLO"
})
.into();
let ast = parse("$length($lowercase(text))").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(5)));
}
#[test]
fn test_conditional_expression() {
let data: JValue = json!({
"score": 85
})
.into();
let ast = parse(r#"score >= 80 ? "Pass" : "Fail""#).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!("Pass")));
}
#[test]
fn test_array_literal() {
let data: JValue = json!({
"a": 1,
"b": 2,
"c": 3
})
.into();
let ast = parse("[a, b, c]").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!([1, 2, 3])));
}
#[test]
fn test_object_literal() {
let data: JValue = json!({
"x": 10,
"y": 20
})
.into();
let ast = parse(r#"{"sum": x + y, "product": x * y}"#).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!({"sum": 30.0, "product": 200.0})));
}
#[test]
fn test_range_operator() {
let data = JValue::Null;
let ast = parse("1..5").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!([1, 2, 3, 4, 5])));
}
#[test]
fn test_in_operator() {
let data: JValue = json!({
"value": 3,
"list": [1, 2, 3, 4, 5]
})
.into();
let ast = parse("value in list").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::Bool(true));
}
#[test]
fn test_complex_real_world_example() {
let data: JValue = json!({
"order": {
"id": "ORD-123",
"items": [
{"name": "Laptop", "price": 1000, "quantity": 1},
{"name": "Mouse", "price": 25, "quantity": 2}
],
"customer": {
"name": "Alice Smith",
"type": "premium"
},
"discount_rate": 0.1
}
})
.into();
let ast = parse("order.customer.name").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!("Alice Smith")));
let ast = parse(r#"order.customer.type = "premium""#).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::Bool(true));
}
#[test]
fn test_missing_field_returns_undefined() {
let data: JValue = json!({
"name": "Alice"
})
.into();
let ast = parse("missing_field").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::Undefined);
}
#[test]
fn test_deep_nesting() {
let data: JValue = json!({
"a": {
"b": {
"c": {
"d": {
"e": "deep value"
}
}
}
}
})
.into();
let ast = parse("a.b.c.d.e").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!("deep value")));
}
#[test]
fn test_multiple_operations() {
let data: JValue = json!({
"x": 10,
"y": 20,
"z": 30
})
.into();
let ast = parse("(x + y) * z / 2").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(450.0)));
}
#[test]
fn test_sum_function() {
let data: JValue = json!({
"numbers": [1, 2, 3, 4, 5]
})
.into();
let ast = parse("$sum(numbers)").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(15.0)));
}
#[test]
fn test_count_function() {
let data: JValue = json!({
"items": [1, 2, 3, 4, 5]
})
.into();
let ast = parse("$count(items)").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(5)));
}
#[test]
fn test_nested_conditionals() {
let data: JValue = json!({
"score": 75
})
.into();
let ast =
parse(r#"score >= 90 ? "A" : (score >= 80 ? "B" : (score >= 70 ? "C" : "F"))"#).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!("C")));
}
#[test]
fn test_block_expression() {
let data = JValue::Null;
let ast = parse("(1; 2; 3)").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(3)));
}
#[test]
fn test_unary_negation() {
let data: JValue = json!({
"value": 42
})
.into();
let ast = parse("-value").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(-42.0)));
}
#[test]
fn test_modulo_operator() {
let data: JValue = json!({
"dividend": 17,
"divisor": 5
})
.into();
let ast = parse("dividend % divisor").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(2.0)));
}
#[test]
fn test_comparison_operators() {
let data: JValue = json!({
"a": 10,
"b": 20
})
.into();
let ast = parse("a < b").unwrap();
let mut evaluator = Evaluator::new();
assert_eq!(evaluator.evaluate(&ast, &data).unwrap(), JValue::Bool(true));
let ast = parse("a <= b").unwrap();
let mut evaluator = Evaluator::new();
assert_eq!(evaluator.evaluate(&ast, &data).unwrap(), JValue::Bool(true));
let ast = parse("b > a").unwrap();
let mut evaluator = Evaluator::new();
assert_eq!(evaluator.evaluate(&ast, &data).unwrap(), JValue::Bool(true));
let ast = parse("b >= a").unwrap();
let mut evaluator = Evaluator::new();
assert_eq!(evaluator.evaluate(&ast, &data).unwrap(), JValue::Bool(true));
let ast = parse("a = 10").unwrap();
let mut evaluator = Evaluator::new();
assert_eq!(evaluator.evaluate(&ast, &data).unwrap(), JValue::Bool(true));
let ast = parse("a != b").unwrap();
let mut evaluator = Evaluator::new();
assert_eq!(evaluator.evaluate(&ast, &data).unwrap(), JValue::Bool(true));
}
#[test]
fn test_string_comparison() {
let data: JValue = json!({
"name1": "Alice",
"name2": "Bob"
})
.into();
let ast = parse("name1 < name2").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::Bool(true));
}
#[test]
fn test_empty_array() {
let data = JValue::Null;
let ast = parse("[]").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!([])));
}
#[test]
fn test_empty_object() {
let data = JValue::Null;
let ast = parse("{}").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!({})));
}
#[test]
fn test_error_undefined_variable() {
let data = JValue::Null;
let ast = parse("$undefined").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::Null);
}
#[test]
fn test_error_type_mismatch() {
let data: JValue = json!({
"text": "hello",
"number": 42
})
.into();
let ast = parse("text + number").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data);
assert!(result.is_err());
}
#[test]
fn test_error_division_by_zero() {
let data: JValue = json!({
"value": 10
})
.into();
let ast = parse("value / 0").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data);
assert!(result.is_err());
}
#[test]
fn test_with_variables() {
let data: JValue = json!({
"price": 100
})
.into();
let ast = parse("price * $discount").unwrap();
let mut context = Context::new();
context.bind("discount".to_string(), json!(0.9).into());
let mut evaluator = Evaluator::with_context(context);
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(90.0)));
}
#[test]
fn test_deep_recursion_does_not_overflow_native_stack() {
let handle = std::thread::Builder::new()
.stack_size(1024 * 1024) .spawn(|| {
let data = JValue::Null;
let ast = parse("($inf := function($n){$n+$inf($n-1)}; $inf(5))").unwrap();
let mut evaluator = Evaluator::new();
match evaluator.evaluate(&ast, &data) {
Ok(v) => format!("Ok({v:?})"),
Err(e) => format!("Err({e})"),
}
})
.unwrap();
let outcome = handle
.join()
.expect("evaluation overflowed the native stack instead of returning a graceful error");
assert!(
outcome.contains("U1001"),
"expected a U1001 stack-overflow error, got: {outcome}"
);
}
#[test]
fn test_max_stack_depth_raises_d1011_not_u1001() {
let data = JValue::Null;
let ast = parse("($inf := function($n){$n+$inf($n-1)}; $inf(5))").unwrap();
let context = Context::new();
let options = jsonata_core::evaluator::EvaluatorOptions {
max_stack_depth: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(context, options);
let result = evaluator.evaluate(&ast, &data);
let err = result.expect_err("expected a D1011 stack-overflow error");
assert!(
err.to_string().contains("D1011"),
"expected D1011, got: {err}"
);
}
#[test]
fn test_max_stack_depth_above_hardcoded_ceiling_still_raises_u1001() {
let handle = std::thread::Builder::new()
.stack_size(1024 * 1024)
.spawn(|| {
let data = JValue::Null;
let ast = parse("($inf := function($n){$n+$inf($n-1)}; $inf(5))").unwrap();
let options = jsonata_core::evaluator::EvaluatorOptions {
max_stack_depth: Some(100_000),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
match evaluator.evaluate(&ast, &data) {
Ok(v) => format!("Ok({v:?})"),
Err(e) => format!("Err({e})"),
}
})
.unwrap();
let outcome = handle.join().unwrap();
assert!(outcome.contains("U1001"), "expected U1001, got: {outcome}");
}
#[test]
fn test_object_construction_drops_undefined_valued_keys() {
let data: JValue = json!({"a": 1}).into();
let ast = parse(r#"{ "keep": a, "drop": b }"#).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!({"keep": 1})));
}
#[test]
fn test_object_construction_drops_undefined_valued_dotted_path() {
let data: JValue = json!({"a": {}}).into();
let ast = parse(r#"{ "k": a.b.c }"#).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!({})));
}
#[test]
fn test_object_construction_keeps_explicit_null() {
let data = JValue::Null;
let ast = parse(r#"{ "k": $exists(x) ? x : null }"#).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!({"k": null})));
}
#[test]
fn test_recursive_lambda_ids_do_not_collide() {
let expr = "λ($f) { λ($x) { $x($x) }( λ($g) { $f( (λ($a) {$g($g)($a)}))})}\
(λ($f) { λ($n) { $n < 2 ? 1 : $n * $f($n - 1) } })(6)";
let ast = parse(expr).unwrap();
let data = JValue::Null;
for i in 0..3000 {
let mut evaluator = Evaluator::new();
let result = evaluator
.evaluate(&ast, &data)
.unwrap_or_else(|e| panic!("iteration {i} failed: {e}"));
assert_eq!(result, JValue::from(json!(720.0)), "iteration {i}");
}
}
fn tuple_at_values(v: &JValue) -> Vec<JValue> {
match v {
JValue::Array(arr) => arr.iter().flat_map(tuple_at_values).collect(),
JValue::Object(o) if o.get("__tuple__").and_then(|b| b.as_bool()) == Some(true) => {
match o.get("@") {
Some(inner) => tuple_at_values(inner),
None => vec![],
}
}
other => vec![other.clone()],
}
}
fn dataset5() -> JValue {
let s = include_str!("jsonata-js/test/test-suite/datasets/dataset5.json");
serde_json::from_str::<serde_json::Value>(s).unwrap().into()
}
fn eval_tuple_at(expr: &str) -> Vec<JValue> {
let ast = parse(expr).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &dataset5()).unwrap();
tuple_at_values(&result)
}
#[test]
fn test_percent_in_predicate_on_parent_step() {
let got = eval_tuple_at("Account.Order.Product.Price.%[%.OrderID='order103'].SKU");
let want: Vec<JValue> = ["0406654608", "0406634348"]
.iter()
.map(|s| JValue::from(json!(s)))
.collect();
assert_eq!(got, want);
}
#[test]
fn test_percent_chain_in_predicate() {
let got = eval_tuple_at("Account.Order.Product[%.%.`Account Name`='Firefly'].SKU");
let want: Vec<JValue> = ["0406654608", "0406634348", "040657863", "0406654603"]
.iter()
.map(|s| JValue::from(json!(s)))
.collect();
assert_eq!(got, want);
}
#[test]
fn test_percent_in_two_sort_terms() {
let got = eval_tuple_at("Account.Order.Product.SKU^(%.Price, >%.%.OrderID)");
let want: Vec<JValue> = ["0406634348", "040657863", "0406654608", "0406654603"]
.iter()
.map(|s| JValue::from(json!(s)))
.collect();
assert_eq!(got, want);
}
#[test]
fn test_timeout_raises_d1012() {
let data = JValue::Null;
let expr_str = "$map([1..200000], function($x){$x*2})";
let ast = parse(expr_str).unwrap();
let options = jsonata_core::evaluator::EvaluatorOptions {
timeout_ms: Some(1), ..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let result = evaluator.evaluate(&ast, &data);
let err = result.expect_err("expected a D1012 timeout error");
assert!(
err.to_string().contains("D1012"),
"expected D1012, got: {err}"
);
}
#[test]
fn test_no_timeout_configured_never_raises_d1012() {
let data = JValue::Null;
let expr_str = "$map([1..200000], function($x){$x*2})";
let ast = parse(expr_str).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data);
assert!(result.is_ok(), "expected Ok, got: {result:?}");
}
#[test]
fn test_tco_trampoline_infinite_recursion_raises_d1012_timeout() {
let data = JValue::Null;
let expr_str = "($inf := function(){$inf()}; $inf())";
let ast = parse(expr_str).unwrap();
let options = jsonata_core::evaluator::EvaluatorOptions {
timeout_ms: Some(5),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator
.evaluate(&ast, &data)
.expect_err("expected a D1012 timeout error");
assert!(
err.to_string().contains("D1012"),
"expected D1012, got: {err}"
);
}
#[test]
fn test_tco_trampoline_no_timeout_configured_completes_normally() {
let data = JValue::Null;
let expr_str = "(\
$count := function($n, $acc){$n <= 0 ? $acc : $count($n - 1, $acc + 1)};\
$count(1000, 0)\
)";
let ast = parse(expr_str).unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
assert_eq!(result, JValue::from(json!(1000.0)));
}
#[test]
fn test_tco_trampoline_no_timeout_configured_still_hits_iteration_cap() {
let data = JValue::Null;
let expr_str = "($inf := function(){$inf()}; $inf())";
let ast = parse(expr_str).unwrap();
let mut evaluator = Evaluator::new();
let err = evaluator
.evaluate(&ast, &data)
.expect_err("expected U1001 at the iteration cap");
assert!(
err.to_string().contains("U1001"),
"expected U1001, got: {err}"
);
}
#[test]
fn test_range_operator_raises_d2015_when_configured() {
let data = JValue::Null;
let ast = parse("[1..1000]").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_range_operator_unaffected_without_max_sequence_length() {
let data = JValue::Null;
let ast = parse("[1..1000]").unwrap();
let mut evaluator = Evaluator::new();
let result = evaluator.evaluate(&ast, &data).unwrap();
match result {
JValue::Array(arr) => assert_eq!(arr.len(), 1000),
other => panic!("expected array, got {other:?}"),
}
}
#[test]
fn test_path_mapping_raises_d2015() {
let data: JValue = serde_json::json!({
"items": (0..1000).map(|i| serde_json::json!({"name": format!("item{i}")})).collect::<Vec<_>>()
}).into();
let ast = parse("items.name").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_wildcard_raises_d2015() {
let mut obj = serde_json::Map::new();
for i in 0..1000 {
obj.insert(format!("k{i}"), serde_json::json!(i));
}
let data: JValue = serde_json::Value::Object(obj).into();
let ast = parse("*").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_descendant_raises_d2015() {
let data: JValue = serde_json::json!({
"items": (0..1000).map(|i| serde_json::json!({"v": i})).collect::<Vec<_>>()
})
.into();
let ast = parse("**").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_path_single_step_fast_path_raises_d2015() {
let data: JValue = serde_json::json!({
"items": [(0..1000).map(|i| serde_json::json!({"name": format!("item{i}")})).collect::<Vec<_>>()]
}).into();
let ast = parse("items.name").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_path_variable_field_fast_path_raises_d2015() {
let data: JValue = serde_json::json!({
"items": (0..1000).map(|i| serde_json::json!({"name": format!("item{i}")})).collect::<Vec<_>>()
}).into();
let ast = parse("($v := items; $v.name)").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_bare_single_step_path_over_root_array_raises_d2015() {
let data: JValue = serde_json::json!((0..1000)
.map(|i| serde_json::json!({"name": format!("item{i}")}))
.collect::<Vec<_>>())
.into();
let ast = parse("name").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_map_generic_path_raises_d2015() {
let data: JValue = serde_json::json!({
"items": (0..1000).map(|i| serde_json::json!({"a": i})).collect::<Vec<_>>()
})
.into();
let ast = parse("$map(items, function($x){$x.*})").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_map_compiled_fast_path_raises_d2015() {
let data: JValue = serde_json::json!({
"items": (0..1000).map(|i| serde_json::json!(i)).collect::<Vec<_>>()
})
.into();
let ast = parse("$map(items, function($x){$x*2})").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_filter_generic_path_raises_d2015() {
let data: JValue = serde_json::json!({
"items": (0..1000).map(|i| serde_json::json!({"a": i})).collect::<Vec<_>>()
})
.into();
let ast = parse("$filter(items, function($x){$x.* != null})").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_filter_compiled_fast_path_raises_d2015() {
let data: JValue = serde_json::json!({
"items": (0..1000).map(|i| serde_json::json!(i)).collect::<Vec<_>>()
})
.into();
let ast = parse("$filter(items, function($x){$x >= 0})").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_map_eval_compiled_inner_raises_d2015() {
let data: JValue = serde_json::json!({
"outer": [1],
"items": (0..1000).collect::<Vec<i32>>()
})
.into();
let ast = parse("$map(outer, function($x){($map(items, function($y){$y*2}); 1)})").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_map_eval_compiled_inner_raises_d1012_timeout() {
let data: JValue = serde_json::json!({
"outer": [1],
"items": (0..2_000_000).collect::<Vec<i32>>()
})
.into();
let ast = parse("$map(outer, function($x){($map(items, function($y){$y*2}); 1)})").unwrap();
let options = EvaluatorOptions {
timeout_ms: Some(200),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D1012");
assert!(err.to_string().contains("D1012"), "got: {err}");
}
#[test]
fn test_filter_eval_compiled_inner_raises_d2015() {
let data: JValue = serde_json::json!({
"outer": [1],
"items": (0..1000).collect::<Vec<i32>>()
})
.into();
let ast = parse("$map(outer, function($x){($filter(items, function($y){$y>=0}); 1)})").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_filter_eval_compiled_inner_raises_d1012_timeout() {
let data: JValue = serde_json::json!({
"outer": [1],
"items": (0..2_000_000).collect::<Vec<i32>>()
})
.into();
let ast = parse("$map(outer, function($x){($filter(items, function($y){$y>=0}); 1)})").unwrap();
let options = EvaluatorOptions {
timeout_ms: Some(200),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D1012");
assert!(err.to_string().contains("D1012"), "got: {err}");
}
#[test]
fn test_reduce_eval_compiled_inner_raises_d1012_timeout() {
let data: JValue = serde_json::json!({
"outer": [1],
"items": (0..2_000_000).collect::<Vec<i32>>()
})
.into();
let ast = parse("$map(outer, function($x){($reduce(items, function($acc,$y){$acc+$y}); 1)})")
.unwrap();
let options = EvaluatorOptions {
timeout_ms: Some(200),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D1012");
assert!(err.to_string().contains("D1012"), "got: {err}");
}
#[test]
fn test_path_filter_eval_compiled_inner_raises_d2015() {
let data: JValue = serde_json::json!({
"outer": [1],
"items": (0..1000).map(|i| serde_json::json!({"val": i})).collect::<Vec<_>>()
})
.into();
let ast = parse("$map(outer, function($x){(items[val>=0]; 1)})").unwrap();
let options = EvaluatorOptions {
max_sequence_length: Some(10),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D2015");
assert!(err.to_string().contains("D2015"), "got: {err}");
}
#[test]
fn test_path_filter_eval_compiled_inner_raises_d1012_timeout() {
let data: JValue = serde_json::json!({
"outer": [1],
"items": (0..2_000_000).map(|i| serde_json::json!({"val": i})).collect::<Vec<_>>()
})
.into();
let ast = parse("$map(outer, function($x){(items[val>=0]; 1)})").unwrap();
let options = EvaluatorOptions {
timeout_ms: Some(200),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D1012");
assert!(err.to_string().contains("D1012"), "got: {err}");
}
#[test]
fn test_map_stored_lambda_bypass_raises_d1012_timeout() {
let data: JValue = serde_json::json!({
"bigArray": (0..2_000_000).collect::<Vec<i32>>()
})
.into();
let ast = parse("($storedFn := function($x){$x*2}; $map(bigArray, $storedFn))").unwrap();
let options = EvaluatorOptions {
timeout_ms: Some(50),
..Default::default()
};
let mut evaluator = Evaluator::with_options(Context::new(), options);
let err = evaluator.evaluate(&ast, &data).expect_err("expected D1012");
assert!(err.to_string().contains("D1012"), "got: {err}");
}
#[test]
fn test_deeply_nested_arithmetic_does_not_overflow_native_stack_at_parse_time() {
let handle = std::thread::Builder::new()
.stack_size(1024 * 1024) .spawn(|| {
let expr_str = format!("({})", vec!["1"; 200_000].join("+"));
match parse(&expr_str) {
Ok(_) => "Ok".to_string(),
Err(e) => format!("Err({e})"),
}
})
.unwrap();
let outcome = handle.join().expect(
"expression parsing overflowed the native stack instead of returning a graceful error",
);
assert!(
outcome.starts_with("Err"),
"expected a graceful depth-limit error, got: {outcome}"
);
assert!(
outcome.contains("U1002"),
"expected the U1002 depth-limit error code, got: {outcome}"
);
}
#[test]
fn test_reasonable_nesting_still_parses_successfully() {
let expr_str = format!("({})", vec!["1"; 200].join("+"));
let result = parse(&expr_str);
assert!(
result.is_ok(),
"expected reasonable nesting to parse fine, got: {result:?}"
);
}
#[test]
fn test_deeply_nested_parens_does_not_overflow_native_stack_at_parse_time() {
let handle = std::thread::Builder::new()
.stack_size(1024 * 1024) .spawn(|| {
let n = 5_000;
let expr_str = format!("{}1{}", "(".repeat(n), ")".repeat(n));
match parse(&expr_str) {
Ok(_) => "Ok".to_string(),
Err(e) => format!("Err({e})"),
}
})
.unwrap();
let outcome = handle.join().expect(
"expression parsing overflowed the native stack instead of returning a graceful error",
);
assert!(
outcome.starts_with("Err") && outcome.contains("U1002"),
"expected a graceful U1002 depth-limit error, got: {outcome}"
);
}
#[test]
fn test_reasonable_paren_nesting_still_parses() {
let n = 50;
let expr_str = format!("{}1{}", "(".repeat(n), ")".repeat(n));
let result = parse(&expr_str);
assert!(
result.is_ok(),
"reasonable paren nesting should parse fine, got: {result:?}"
);
}
#[test]
fn test_deeply_nested_arithmetic_caught_at_parse_construction_time() {
let expr_str = format!("({})", vec!["1"; 200_000].join("+"));
let result = parse(&expr_str);
match result {
Err(e) => {
let msg = format!("{e}");
assert!(msg.contains("U1002"), "expected U1002, got: {msg}");
assert!(
msg.contains("while parsing expression"),
"expected the parser-level guard's message (not ast_transform's), got: {msg}"
);
}
Ok(_) => panic!("expected a graceful depth-limit error"),
}
}
#[test]
fn test_sibling_subtrees_do_not_inherit_depth() {
let chain = vec!["1"; 400].join("+");
let expr_str = format!("[{chain}, {chain}, {chain}]");
let result = parse(&expr_str);
assert!(
result.is_ok(),
"siblings should each parse independently without inheriting depth from a prior sibling, got: {result:?}"
);
}
#[cfg(feature = "bench")]
#[test]
fn test_large_literal_array_does_not_silently_truncate() {
use jsonata_core::_bench;
let n = 100_000;
let expr_str = format!("[{}]", vec!["1"; n].join(","));
let ast = parse(&expr_str).unwrap();
let data = JValue::Null;
let result = match _bench::compile(&ast) {
Some(prog) => _bench::run(&prog, &data).unwrap(),
None => Evaluator::new().evaluate(&ast, &data).unwrap(),
};
match result {
JValue::Array(arr) => assert_eq!(arr.len(), n, "array literal was silently truncated"),
other => panic!("expected array, got {other:?}"),
}
}
#[cfg(feature = "bench")]
#[test]
fn test_large_literal_object_does_not_silently_truncate() {
use jsonata_core::_bench;
let n = 100_000;
let pairs: Vec<String> = (0..n).map(|i| format!("\"k{i}\": {i}")).collect();
let expr_str = format!("{{{}}}", pairs.join(","));
let ast = parse(&expr_str).unwrap();
let data = JValue::Null;
let result = match _bench::compile(&ast) {
Some(prog) => _bench::run(&prog, &data).unwrap(),
None => Evaluator::new().evaluate(&ast, &data).unwrap(),
};
match result {
JValue::Object(obj) => assert_eq!(obj.len(), n, "object literal was silently truncated"),
other => panic!("expected object, got {other:?}"),
}
}
#[cfg(feature = "bench")]
#[test]
fn test_large_block_does_not_silently_truncate() {
use jsonata_core::_bench;
let n = 70_000;
let block_body = vec!["5"; n].join(";");
let expr_str = format!("[0, ({block_body}), 9]");
let ast = parse(&expr_str).unwrap();
let data = JValue::Null;
let result = match _bench::compile(&ast) {
Some(prog) => _bench::run(&prog, &data).unwrap(),
None => Evaluator::new().evaluate(&ast, &data).unwrap(),
};
assert_eq!(
result,
JValue::array(vec![
JValue::from(0i64),
JValue::from(5i64),
JValue::from(9i64)
]),
"large block silently corrupted a sibling array element via BlockEnd's truncated u16 operand"
);
}
#[cfg(feature = "bench")]
#[test]
fn test_sibling_blocks_do_not_cumulatively_overflow_shared_const_pool() {
use jsonata_core::_bench;
let m = 40_000;
let n = 40_000;
let block_a = (0..m).map(|i| i.to_string()).collect::<Vec<_>>().join(";");
let block_b = (0..n)
.map(|i| (200_000 + i).to_string())
.collect::<Vec<_>>()
.join(";");
let expr_str = format!("{{\"a\": ({block_a}), \"b\": ({block_b})}}");
let ast = parse(&expr_str).unwrap();
let data = JValue::Null;
let result = match _bench::compile(&ast) {
Some(prog) => _bench::run(&prog, &data).unwrap(),
None => Evaluator::new().evaluate(&ast, &data).unwrap(),
};
match result {
JValue::Object(obj) => {
assert_eq!(obj.get("a"), Some(&JValue::from((m - 1) as i64)));
assert_eq!(
obj.get("b"),
Some(&JValue::from((200_000 + n - 1) as i64)),
"sibling block's last value was cumulatively corrupted via const_pool's \
wrapped u16 index"
);
}
other => panic!("expected object, got {other:?}"),
}
}
#[cfg(feature = "bench")]
#[test]
fn test_merge_call_with_more_than_u8_max_args_does_not_silently_truncate() {
use jsonata_core::_bench;
let n = 300;
let objects: Vec<String> = (0..n).map(|i| format!("{{\"k{i}\": {i}}}")).collect();
let expr_str = format!("$merge({})", objects.join(", "));
let ast = parse(&expr_str).unwrap();
let data = JValue::Null;
assert!(
_bench::compile(&ast).is_none(),
"expected the >u8::MAX-arg $merge call to decline bytecode compilation \
(fall back to the tree-walker), but it compiled successfully - the \
u8::MAX arg-count guard appears to be missing or broken"
);
let result = Evaluator::new().evaluate(&ast, &data).unwrap();
match result {
JValue::Object(obj) => {
assert_eq!(
obj.len(),
n,
"merge() with 300 single-key object args silently dropped keys - \
likely the u8 arg-count truncation this guard exists to prevent"
);
for i in 0..n {
assert_eq!(
obj.get(&format!("k{i}")),
Some(&JValue::from(i as i64)),
"missing or wrong value for k{i} in merged result"
);
}
}
other => panic!("expected object, got {other:?}"),
}
}