rulemorph 0.3.4

YAML-based declarative data transformation engine for CSV/JSON to JSON
Documentation
use rulemorph::{DtoLanguage, generate_dto, parse_rule_file};

fn malicious_rule() -> rulemorph::RuleFile {
    parse_rule_file(
        r#"version: 1
input:
  format: json
  json: {}
mappings:
  - target: '["bad\"key"]'
    value: "x"
  - target: "['tick`key']"
    value: "x"
  - target: "['bad*/key']"
    value: "x"
"#,
    )
    .expect("parse rule")
}

#[test]
fn dto_generation_sanitizes_type_name_and_escapes_json_keys() {
    let rule = malicious_rule();
    for language in [
        DtoLanguage::Rust,
        DtoLanguage::TypeScript,
        DtoLanguage::Python,
        DtoLanguage::Go,
        DtoLanguage::Java,
        DtoLanguage::Kotlin,
        DtoLanguage::Swift,
    ] {
        let output = generate_dto(&rule, language, Some("Record { hacked"))
            .expect("generate dto should not fail");
        assert!(!output.contains("Record { hacked"));
        assert!(!output.contains("bad*/key */"));
    }

    let rust =
        generate_dto(&rule, DtoLanguage::Rust, Some("Record { hacked")).expect("generate rust dto");
    assert!(rust.contains(r#"rename = "bad\"key""#));

    let go =
        generate_dto(&rule, DtoLanguage::Go, Some("Record { hacked")).expect("generate go dto");
    assert!(go.contains(r#""json:\"bad\\\"key\"""#));
    assert!(go.contains(r#""json:\"tick`key\"""#));
}

#[test]
fn dto_generation_sanitizes_inferred_structured_keys() {
    let rule = parse_rule_file(
        r#"version: 2
input:
  format: json
  json: {}
mappings:
  - target: payload
    value:
      bad*/key:
        quote"key: "x"
        "slash\\key": "b"
        "line\nkey": "y"
      tick`key:
        - class: true
          "tab\tkey": "z"
      items:
        - id: "a"
          "bad*/item": 1
          "quote\"item": true
          "slash\\item": false
    required: true
  - target: byKey
    expr: ["@out.payload.items", { key_by: ["@item.id"] }]
    required: true
"#,
    )
    .expect("parse rule");

    for language in [
        DtoLanguage::Rust,
        DtoLanguage::TypeScript,
        DtoLanguage::Python,
        DtoLanguage::Go,
        DtoLanguage::Java,
        DtoLanguage::Kotlin,
        DtoLanguage::Swift,
    ] {
        let output = generate_dto(&rule, language, Some("Record { hacked"))
            .expect("generate dto should not fail");
        assert!(!output.contains("Record { hacked"));
        assert!(!output.contains("bad*/key */"));
        assert!(!output.contains("bad*/item */"));
        assert!(!output.contains("quote\"key"));
        assert!(!output.contains("slash\\key"));
        assert!(!output.contains("line\nkey"));
        assert!(!output.contains("tab\tkey"));
    }

    let rust =
        generate_dto(&rule, DtoLanguage::Rust, Some("Record { hacked")).expect("generate rust dto");
    assert!(rust.contains(r#"rename = "bad*/key""#));
    assert!(rust.contains(r#"rename = "quote\"key""#));
    assert!(rust.contains(r#"rename = "slash\\key""#));
    assert!(rust.contains(r#"rename = "line\nkey""#));

    let go =
        generate_dto(&rule, DtoLanguage::Go, Some("Record { hacked")).expect("generate go dto");
    assert!(go.contains(r#""json:\"tick`key\"""#));
    assert!(go.contains(r#""json:\"quote\\\"key\"""#));
    assert!(go.contains(r#""json:\"slash\\\\key\"""#));
    assert!(go.contains(r#""json:\"line\\nkey\"""#));

    let python =
        generate_dto(&rule, DtoLanguage::Python, Some("Record")).expect("generate python dto");
    assert!(python.contains(r#"# json: "bad* /key""#));
    assert!(python.contains(r#"# json: "quote\"key""#));
    assert!(python.contains(r#"# json: "slash\\key""#));
    assert!(python.contains(r#"metadata={"json_key": "quote\"key"}"#));
    assert!(python.contains(r#"metadata={"json_key": "slash\\key"}"#));
    assert!(python.contains(r#"metadata={"json_key": "line\nkey"}"#));

    let typescript = generate_dto(&rule, DtoLanguage::TypeScript, Some("Record"))
        .expect("generate typescript dto");
    assert!(typescript.contains(r#"json: "bad* /key""#));
    assert!(typescript.contains(r#"json: "quote\"key""#));
    assert!(typescript.contains(r#"json: "slash\\key""#));
    assert!(typescript.contains(r#"json: "line\\nkey""#));
    assert!(typescript.contains("{ [key: string]:"));
    assert!(!typescript.contains("Record<string,"));

    let java = generate_dto(&rule, DtoLanguage::Java, Some("Record")).expect("generate java dto");
    assert!(java.contains(r#"@JsonProperty("bad*/key")"#));
    assert!(java.contains(r#"@JsonProperty("quote\"key")"#));
    assert!(java.contains(r#"@JsonProperty("slash\\key")"#));
    assert!(java.contains(r#"@JsonProperty("line\nkey")"#));

    let kotlin =
        generate_dto(&rule, DtoLanguage::Kotlin, Some("Record")).expect("generate kotlin dto");
    assert!(kotlin.contains(r#"@JsonProperty("bad*/key")"#));
    assert!(kotlin.contains(r#"@JsonProperty("quote\"key")"#));
    assert!(kotlin.contains(r#"@JsonProperty("slash\\key")"#));
    assert!(kotlin.contains(r#"@JsonProperty("line\nkey")"#));

    let swift =
        generate_dto(&rule, DtoLanguage::Swift, Some("Record")).expect("generate swift dto");
    assert!(swift.contains(r#"case badKey = "bad*/key""#));
    assert!(swift.contains(r#"case quoteKey = "quote\"key""#));
    assert!(swift.contains(r#"case slashKey = "slash\\key""#));
    assert!(swift.contains(r#"line\nkey"#));
}