rulemorph 0.3.1

YAML-based declarative data transformation engine for CSV/JSON to JSON
Documentation
#[test]
fn test_eval_op_comparison_aliases() {
    let ctx = V2EvalContext::new();
    let cases = [
        ("eq", json!(1), json!("1"), true),
        ("ne", json!(1), json!(2), true),
        ("lt", json!(5), json!(10), true),
        ("lte", json!(10), json!(10), true),
        ("gt", json!(10), json!(5), true),
        ("gte", json!(10), json!(10), true),
        ("match", json!("apple"), json!("^a.*"), true),
    ];

    for (op, left, right, expected) in cases {
        let op_step = V2OpStep {
            op: op.to_string(),
            args: vec![lit(right)],
        };
        let result = eval_v2_op_step(
            &op_step,
            EvalValue::Value(left),
            &json!({}),
            None,
            &json!({}),
            "test",
            &ctx,
        );
        assert!(
            matches!(result, Ok(EvalValue::Value(v)) if v == json!(expected)),
            "op {}",
            op
        );
    }
}

#[test]
fn test_eval_op_comparison_symbols() {
    let ctx = V2EvalContext::new();
    let cases = [
        ("==", json!(1), json!("1"), true),
        ("!=", json!(1), json!(2), true),
        ("<", json!(5), json!(10), true),
        ("<=", json!(10), json!(10), true),
        (">", json!(10), json!(5), true),
        (">=", json!(10), json!(10), true),
        ("~=", json!("apple"), json!("^a.*"), true),
    ];

    for (op, left, right, expected) in cases {
        let op_step = V2OpStep {
            op: op.to_string(),
            args: vec![lit(right)],
        };
        let result = eval_v2_op_step(
            &op_step,
            EvalValue::Value(left),
            &json!({}),
            None,
            &json!({}),
            "test",
            &ctx,
        );
        assert!(
            matches!(result, Ok(EvalValue::Value(v)) if v == json!(expected)),
            "op {}",
            op
        );
    }
}

#[test]
fn test_eval_op_comparison_missing_null_semantics() {
    let ctx = V2EvalContext::new();
    let missing_eq_null = V2OpStep {
        op: "eq".to_string(),
        args: vec![lit(json!(null))],
    };
    let result = eval_v2_op_step(
        &missing_eq_null,
        EvalValue::Missing,
        &json!({}),
        None,
        &json!({}),
        "test",
        &ctx,
    );
    assert!(matches!(result, Ok(EvalValue::Value(v)) if v == json!(true)));

    let missing_ne_value = V2OpStep {
        op: "ne".to_string(),
        args: vec![lit(json!("value"))],
    };
    let result = eval_v2_op_step(
        &missing_ne_value,
        EvalValue::Missing,
        &json!({}),
        None,
        &json!({}),
        "test",
        &ctx,
    );
    assert!(matches!(result, Ok(EvalValue::Value(v)) if v == json!(true)));
}

#[test]
fn test_eval_op_comparison_error_paths() {
    let ctx = V2EvalContext::new();
    let non_numeric_compare = V2OpStep {
        op: "<".to_string(),
        args: vec![lit(json!(1))],
    };
    let err = eval_v2_op_step(
        &non_numeric_compare,
        EvalValue::Value(json!("not-a-number")),
        &json!({}),
        None,
        &json!({}),
        "test",
        &ctx,
    )
    .expect_err("non-numeric comparison should error");
    assert_eq!(err.kind, TransformErrorKind::ExprError);
    assert_eq!(err.message, "comparison operand must be a number");
    assert_eq!(err.path.as_deref(), Some("test"));

    let invalid_regex = V2OpStep {
        op: "~=".to_string(),
        args: vec![lit(json!("["))],
    };
    let err = eval_v2_op_step(
        &invalid_regex,
        EvalValue::Value(json!("apple")),
        &json!({}),
        None,
        &json!({}),
        "test",
        &ctx,
    )
    .expect_err("invalid regex should error");
    assert_eq!(err.kind, TransformErrorKind::ExprError);
    assert!(err.message.starts_with("invalid regex pattern:"));
    assert_eq!(err.path.as_deref(), Some("test.args[0]"));
}