rulemorph 0.2.3

YAML-based declarative data transformation engine for CSV/JSON to JSON
Documentation
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};

use rulemorph::{parse_rule_file, transform_with_base_dir};

fn unique_temp_dir(name: &str) -> PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system time")
        .as_nanos();
    let path = std::env::temp_dir().join(format!("rulemorph-{name}-{nanos}"));
    fs::create_dir_all(&path).expect("create temp dir");
    path
}

#[test]
fn branch_self_cycle_is_rejected() {
    let dir = unique_temp_dir("branch-self-cycle");
    let rule_path = dir.join("self.yaml");
    fs::write(
        &rule_path,
        r#"version: 2
input:
  format: json
  json: {}
steps:
  - branch:
      when: { eq: [1, 1] }
      then: self.yaml
"#,
    )
    .expect("write rule");
    let rule = parse_rule_file(&fs::read_to_string(&rule_path).expect("read rule")).expect("parse");
    let err = transform_with_base_dir(&rule, r#"{"id":1}"#, None, &dir)
        .expect_err("cycle should be rejected");
    assert!(err.to_string().contains("cycle"));
}

#[test]
fn branch_path_must_stay_under_base_dir() {
    let dir = unique_temp_dir("branch-base");
    let outside = unique_temp_dir("branch-outside");
    let outside_rule = outside.join("outside.yaml");
    fs::write(
        &outside_rule,
        r#"version: 2
input:
  format: json
  json: {}
mappings: []
"#,
    )
    .expect("write outside rule");
    let rule = parse_rule_file(&format!(
        r#"version: 2
input:
  format: json
  json: {{}}
steps:
  - branch:
      when: {{ eq: [1, 1] }}
      then: {}
"#,
        outside_rule.display()
    ))
    .expect("parse");
    let err = transform_with_base_dir(&rule, r#"{"id":1}"#, None, &dir)
        .expect_err("outside branch should be rejected");
    assert!(err.to_string().contains("base directory"));
}

#[test]
fn nested_branch_can_reference_parent_within_base_dir() {
    let dir = unique_temp_dir("branch-nested-parent");
    let subdir = dir.join("sub");
    fs::create_dir_all(&subdir).expect("create subdir");
    let main_rule = dir.join("main.yaml");
    let child_rule = subdir.join("child.yaml");
    let common_rule = dir.join("common.yaml");
    fs::write(
        &main_rule,
        r#"version: 2
input:
  format: json
  json: {}
steps:
  - branch:
      when: { eq: [1, 1] }
      then: sub/child.yaml
      return: true
"#,
    )
    .expect("write main");
    fs::write(
        &child_rule,
        r#"version: 2
input:
  format: json
  json: {}
steps:
  - branch:
      when: { eq: [1, 1] }
      then: ../common.yaml
      return: true
"#,
    )
    .expect("write child");
    fs::write(
        &common_rule,
        r#"version: 2
input:
  format: json
  json: {}
mappings:
  - target: ok
    value: true
"#,
    )
    .expect("write common");
    let rule = parse_rule_file(&fs::read_to_string(&main_rule).expect("read main")).expect("parse");
    let output =
        transform_with_base_dir(&rule, r#"{"id":1}"#, None, &dir).expect("nested branch succeeds");
    assert_eq!(output, serde_json::json!([{ "ok": true }]));
}