rulemorph 0.3.2

YAML-based declarative data transformation engine for CSV/JSON to JSON
Documentation
use super::*;
use crate::trace::{
    TraceAttributeValue, canonical_acc_path, canonical_context_path, canonical_input_path,
    canonical_item_path, canonical_out_path, canonical_output_path,
};
use serde_json::json;

#[path = "tests/span_stack.rs"]
mod span_stack;

#[test]
fn canonical_path_helpers_preserve_bracket_notation() {
    assert_eq!(canonical_input_path(r#"input["@id"]"#), r#"@input["@id"]"#);
    assert_eq!(canonical_input_path("input[0].name"), "@input[0].name");
    assert_eq!(canonical_item_path("@item[0].name"), "@item[0].name");
    assert_eq!(canonical_output_path("$.items[0].name"), "$.items[0].name");
}

#[test]
fn canonical_path_helpers_cover_all_namespaces_and_output_prefixes() {
    assert_eq!(canonical_acc_path(r#"acc["total"]"#), r#"@acc["total"]"#);
    assert_eq!(
        canonical_context_path(r#"context["tenant"]"#),
        r#"@context["tenant"]"#
    );
    assert_eq!(canonical_out_path(r#"out["name"]"#), r#"@out["name"]"#);
    assert_eq!(canonical_output_path(""), "$");
    assert_eq!(canonical_output_path("output"), "$.output");
    assert_eq!(canonical_output_path("output.name"), "$.name");
    assert_eq!(canonical_output_path("out.items[0]"), "$.items[0]");
    assert_eq!(canonical_output_path("$[0].name"), "$[0].name");
}

#[test]
fn trace_builder_attributes_are_scalar_metadata() {
    let mut collector = TraceCollector::new(TransformTraceOptions::metadata_only());
    collector.start_record(0, &json!({"name":"alice"}));
    collector
        .emit(TraceEventKind::MappingDecision, TracePhase::Instant)
        .attr_bool("applied", true)
        .attr_index("mapping_index", 1)
        .attr_count("output_count", 2)
        .attr_enum("skip_reason", "none")
        .attr_path("target_path", "$.name")
        .finish(&mut collector);

    let trace = collector.finish();
    let event = trace.records[0]
        .events
        .iter()
        .find(|event| event.kind == TraceEventKind::MappingDecision)
        .expect("mapping decision");
    assert_eq!(
        event.attributes.get("applied"),
        Some(&TraceAttributeValue::Bool(true))
    );
    assert_eq!(
        event
            .attributes
            .get("mapping_index")
            .and_then(|value| match value {
                TraceAttributeValue::Number(number) => number.as_u64(),
                _ => None,
            }),
        Some(1)
    );
    assert_eq!(
        event
            .attributes
            .get("output_count")
            .and_then(|value| match value {
                TraceAttributeValue::Number(number) => number.as_u64(),
                _ => None,
            }),
        Some(2)
    );
    assert_eq!(
        event.attributes.get("skip_reason"),
        Some(&TraceAttributeValue::String("none".to_string()))
    );
    assert_eq!(
        event.attributes.get("target_path"),
        Some(&TraceAttributeValue::String("$.name".to_string()))
    );
    assert!(event.inputs.is_empty());
    assert!(event.output.is_none());
}

#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "semantic path namespace mismatch")]
fn canonical_path_helpers_detect_namespace_mismatch() {
    let _ = canonical_input_path("@item.name");
}