#![cfg(feature = "trace")]
use datalogic_rs::Engine;
use serde_json::json;
#[test]
fn test_trace_simple_comparison() {
let engine = Engine::new();
let run = engine
.trace()
.eval_into::<serde_json::Value, _, _>(r#"{">=": [{"var": "age"}, 18]}"#, r#"{"age": 25}"#);
assert_eq!(run.result.as_ref().unwrap(), &json!(true));
assert!(run.expression_tree.expression.contains(">="));
assert!(!run.steps.is_empty());
for step in &run.steps {
assert!(step.result.is_some() || step.error.is_some());
}
}
#[test]
fn test_trace_proposal_example() {
let engine = Engine::new();
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"and": [{">=": [{"var": "age"}, 18]}, true]}"#,
r#"{"age": 25}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!(true));
assert!(run.expression_tree.expression.contains("and"));
assert!(!run.expression_tree.children.is_empty());
}
#[test]
fn test_trace_short_circuit_and() {
let engine = Engine::new();
let run = engine
.trace()
.eval_into::<serde_json::Value, _, _>(r#"{"and": [false, {"var": "expensive"}]}"#, r#"{}"#);
assert_eq!(run.result.as_ref().unwrap(), &json!(false));
}
#[test]
fn test_trace_short_circuit_or() {
let engine = Engine::new();
let run = engine
.trace()
.eval_into::<serde_json::Value, _, _>(r#"{"or": [true, {"var": "expensive"}]}"#, r#"{}"#);
assert_eq!(run.result.as_ref().unwrap(), &json!(true));
}
#[test]
fn test_trace_map_operator() {
let engine = Engine::new();
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"map": [[1, 2, 3], {"*": [{"var": ""}, 2]}]}"#,
r#"{}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!([2, 4, 6]));
let iteration_steps: Vec<_> = run
.steps
.iter()
.filter(|s| s.iteration_index.is_some())
.collect();
assert!(!iteration_steps.is_empty());
for step in &iteration_steps {
assert!(step.iteration_total == Some(3));
}
}
#[test]
fn test_trace_filter_operator() {
let engine = Engine::new();
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"filter": [[1, 2, 3, 4, 5], {">": [{"var": ""}, 2]}]}"#,
r#"{}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!([3, 4, 5]));
}
#[test]
fn test_trace_reduce_operator() {
let engine = Engine::new();
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"reduce": [[1, 2, 3, 4], {"+": [{"var": "accumulator"}, {"var": "current"}]}, 0]}"#,
r#"{}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!(10));
}
#[test]
fn test_trace_if_operator() {
let engine = Engine::new();
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"if": [{"var": "active"}, "yes", "no"]}"#,
r#"{"active": true}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!("yes"));
}
#[test]
fn test_trace_nested_operators() {
let engine = Engine::new();
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"and": [{">": [{"var": "x"}, 0]}, {"<": [{"var": "x"}, 100]}]}"#,
r#"{"x": 50}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!(true));
assert!(!run.expression_tree.children.is_empty());
}
#[test]
fn test_expression_tree_structure() {
let engine = Engine::new();
let run = engine
.trace()
.eval_into::<serde_json::Value, _, _>(r#"{">=": [{"var": "age"}, 18]}"#, r#"{"age": 25}"#);
assert!(run.expression_tree.id > 0);
assert!(run.expression_tree.expression.contains(">="));
assert_eq!(run.expression_tree.children.len(), 1);
assert!(run.expression_tree.children[0].id > 0);
assert_ne!(run.expression_tree.children[0].id, run.expression_tree.id);
assert!(run.expression_tree.children[0].expression.contains("var"));
}
#[test]
fn test_literals_no_separate_steps() {
let engine = Engine::new();
let run = engine
.trace()
.eval_into::<serde_json::Value, _, _>(r#"{"==": [{"var": "x"}, 1]}"#, r#"{"x": 1}"#);
assert_eq!(run.result.as_ref().unwrap(), &json!(true));
assert!(!run.steps.is_empty());
for step in &run.steps {
assert!(step.result.is_some());
}
}
#[test]
fn test_step_context() {
let engine = Engine::new();
let run = engine
.trace()
.eval_into::<serde_json::Value, _, _>(r#"{"var": "name"}"#, r#"{"name": "Alice"}"#);
assert_eq!(run.result.as_ref().unwrap(), &json!("Alice"));
assert!(!run.steps.is_empty());
let step = &run.steps[0];
assert!(step.context.get("name").is_some());
}
#[test]
fn test_trace_quantifier_operators() {
let engine = Engine::new();
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"all": [[1, 2, 3], {">": [{"var": ""}, 0]}]}"#,
r#"{}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!(true));
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"some": [[1, 2, 3], {">": [{"var": ""}, 2]}]}"#,
r#"{}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!(true));
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"none": [[1, 2, 3], {">": [{"var": ""}, 5]}]}"#,
r#"{}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!(true));
}
#[test]
fn test_trace_ternary_operator() {
let engine = Engine::new();
let run = engine
.trace()
.eval_into::<serde_json::Value, _, _>(r#"{"?:": [true, "yes", "no"]}"#, r#"{}"#);
assert_eq!(run.result.as_ref().unwrap(), &json!("yes"));
}
#[test]
fn test_trace_coalesce_operator() {
let engine = Engine::new();
let run = engine
.trace()
.eval_into::<serde_json::Value, _, _>(r#"{"??": [null, null, "found"]}"#, r#"{}"#);
assert_eq!(run.result.as_ref().unwrap(), &json!("found"));
}
#[test]
fn test_trace_with_error() {
let engine = Engine::new();
let run = engine
.trace()
.eval_into::<serde_json::Value, _, _>(r#"{"var": "missing.path"}"#, r#"{}"#);
assert_eq!(run.result.as_ref().unwrap(), &json!(null));
}
#[test]
fn test_trace_arithmetic() {
let engine = Engine::new();
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"+": [{"*": [{"var": "x"}, 3]}, 4]}"#,
r#"{"x": 2}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!(10));
assert!(!run.steps.is_empty());
assert!(run.expression_tree.expression.contains("+"));
}
#[test]
fn test_trace_string_operators() {
let engine = Engine::new();
let run = engine.trace().eval_into::<serde_json::Value, _, _>(
r#"{"cat": ["Hello, ", {"var": "name"}]}"#,
r#"{"name": "World"}"#,
);
assert_eq!(run.result.as_ref().unwrap(), &json!("Hello, World"));
}
#[test]
fn test_trace_custom_operator_error_propagation() {
use bumpalo::Bump;
use datalogic_rs::operator::EvalContext;
use datalogic_rs::{CustomOperator, DataValue, Error, Result as DLResult};
struct FailOp;
impl CustomOperator for FailOp {
fn evaluate<'a>(
&self,
_args: &[&'a DataValue<'a>],
_ctx: &mut EvalContext<'_, 'a>,
_arena: &'a Bump,
) -> DLResult<&'a DataValue<'a>> {
Err(Error::custom_message("boom"))
}
}
let engine = Engine::builder().add_operator("fail_op", FailOp).build();
let run = engine.trace().eval_str(r#"{"fail_op": []}"#, "null");
let err = run.result.expect_err("FailOp returned Err");
assert_eq!(err.operator(), Some("fail_op"));
assert!(
err.to_string().contains("boom"),
"error display should contain 'boom', got: {err}",
);
assert!(
!run.steps.is_empty(),
"trace should record at least one step before the failure",
);
assert!(
run.expression_tree.expression.contains("fail_op"),
"expression tree should mention 'fail_op': {:?}",
run.expression_tree
);
}