#[test]
fn trace_v2_map_nested_error_closes_collection_and_map_spans() {
let yaml = r#"
version: 2
input:
format: json
mappings:
- target: "items"
expr:
- "@input.items"
- map:
- divide: [0]
"#;
let rule = parse_rule(yaml);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
transform_input_with_trace(
&rule,
InputData::Text(r#"[{"items":[1]}]"#),
None,
&TransformTraceOptions::raw(),
)
}));
assert!(result.is_ok(), "map nested error must not leave open spans");
let err = result.expect("no panic").expect_err("traced error");
assert_eq!(err.error.kind, rulemorph::TransformErrorKind::ExprError);
assert_trace_shape(&err.trace);
}
#[test]
fn trace_v2_item_scoped_collection_ops_match_normal() {
let cases = [
(
"flat_map",
r#"
version: 2
input:
format: json
mappings:
- target: "tags"
expr:
- "@input.users"
- flat_map: ["@item.tags"]
"#,
json!([{ "tags": ["a", "b", "c"] }]),
),
(
"group_by",
r#"
version: 2
input:
format: json
mappings:
- target: "by_role"
expr:
- "@input.users"
- group_by: ["@item.role"]
"#,
json!([{ "by_role": {
"admin": [
{"id":"u1","name":"Alice","role":"admin","active":false,"tags":["a","b"]},
{"id":"u3","name":"Carol","role":"admin","active":true,"tags":[]}
],
"member": [
{"id":"u2","name":"Bob","role":"member","active":true,"tags":["c"]}
]
} }]),
),
(
"key_by",
r#"
version: 2
input:
format: json
mappings:
- target: "by_id"
expr:
- "@input.users"
- key_by: ["@item.id"]
"#,
json!([{ "by_id": {
"u1": {"id":"u1","name":"Alice","role":"admin","active":false,"tags":["a","b"]},
"u2": {"id":"u2","name":"Bob","role":"member","active":true,"tags":["c"]},
"u3": {"id":"u3","name":"Carol","role":"admin","active":true,"tags":[]}
} }]),
),
(
"partition",
r#"
version: 2
input:
format: json
mappings:
- target: "active_groups"
expr:
- "@input.users"
- partition: ["@item.active"]
"#,
json!([{ "active_groups": [
[
{"id":"u2","name":"Bob","role":"member","active":true,"tags":["c"]},
{"id":"u3","name":"Carol","role":"admin","active":true,"tags":[]}
],
[
{"id":"u1","name":"Alice","role":"admin","active":false,"tags":["a","b"]}
]
] }]),
),
(
"distinct_by",
r#"
version: 2
input:
format: json
mappings:
- target: "first_by_role"
expr:
- "@input.users"
- distinct_by: ["@item.role"]
"#,
json!([{ "first_by_role": [
{"id":"u1","name":"Alice","role":"admin","active":false,"tags":["a","b"]},
{"id":"u2","name":"Bob","role":"member","active":true,"tags":["c"]}
] }]),
),
(
"find",
r#"
version: 2
input:
format: json
mappings:
- target: "first_active"
expr:
- "@input.users"
- find: ["@item.active"]
"#,
json!([{ "first_active": {"id":"u2","name":"Bob","role":"member","active":true,"tags":["c"]} }]),
),
(
"find_index",
r#"
version: 2
input:
format: json
mappings:
- target: "first_active_index"
expr:
- "@input.users"
- find_index: ["@item.active"]
"#,
json!([{ "first_active_index": 1 }]),
),
];
let input = r#"[{"users":[
{"id":"u1","name":"Alice","role":"admin","active":false,"tags":["a","b"]},
{"id":"u2","name":"Bob","role":"member","active":true,"tags":["c"]},
{"id":"u3","name":"Carol","role":"admin","active":true,"tags":[]}
]}]"#;
for (name, yaml, expected) in cases {
assert_traced_output_matches_normal(yaml, input, expected);
let rule = parse_rule_file(yaml).unwrap_or_else(|err| panic!("{name} parse: {err:?}"));
let traced = transform_input_with_trace(
&rule,
InputData::Text(input),
None,
&TransformTraceOptions::raw(),
)
.unwrap_or_else(|err| panic!("{name} traced transform: {err:?}"));
assert!(
iter_trace_events(&traced.trace).iter().any(|event| {
event.kind == TraceEventKind::OpStart && event.operator.as_deref() == Some(name)
}),
"missing op_start for {name}"
);
}
}
#[test]
fn trace_v2_fold_preserves_acc_and_item_scope() {
let yaml = r#"
version: 2
input:
format: json
mappings:
- target: "sum"
expr:
- "@input.numbers"
- fold:
- 0
- ["@acc", { "+": "@item" }]
"#;
assert_traced_output_matches_normal(yaml, r#"[{"numbers":[1,2,3]}]"#, json!([{ "sum": 6.0 }]));
}
#[test]
fn trace_v2_let_binding_survives_nested_map_steps() {
let yaml = r#"
version: 2
input:
format: json
mappings:
- target: "totals"
expr:
- "@input.prices"
- map:
- let: { tax: 1.1 }
- multiply: ["@tax"]
"#;
assert_traced_output_matches_normal(
yaml,
r#"[{"prices":[100,200]}]"#,
json!([{ "totals": [110.00000000000001, 220.00000000000003] }]),
);
}