rulemorph 0.2.3

YAML-based declarative data transformation engine for CSV/JSON to JSON
Documentation
use std::time::Instant;

use rulemorph::{parse_rule_file, transform};
use serde_json::json;

const PERF_RULES: &str = r#"
version: 1
input:
  format: json
  json: {}
mappings:
  - target: "id"
    source: "id"
  - target: "user_name"
    expr:
      op: "lookup_first"
      args:
        - { ref: "context.users" }
        - "id"
        - { ref: "input.user_id" }
        - "name"
  - target: "tags"
    expr:
      op: "lookup"
      args:
        - { ref: "context.tags" }
        - "id"
        - { ref: "input.tag_id" }
        - "value"
"#;

#[test]
#[ignore]
fn perf_lookup_transform() {
    let record_count = env_usize("PERF_RECORDS", 10_000);
    let iterations = env_usize("PERF_ITERS", 5);
    let user_count = env_usize("PERF_USERS", 100);
    let tag_count = env_usize("PERF_TAGS", 100);

    let rule = parse_rule_file(PERF_RULES).expect("failed to parse perf rules");
    let input = build_input(record_count, user_count, tag_count);
    let context = build_context(user_count, tag_count);

    let start = Instant::now();
    let mut last_len = 0;
    for _ in 0..iterations {
        let output = transform(&rule, &input, Some(&context)).expect("transform failed");
        last_len = output.as_array().map(|items| items.len()).unwrap_or(0);
        std::hint::black_box(output);
    }
    let elapsed = start.elapsed();

    assert_eq!(last_len, record_count);
    eprintln!(
        "perf_lookup_transform records={} iters={} elapsed_ms={}",
        record_count,
        iterations,
        elapsed.as_millis()
    );
}

fn build_context(user_count: usize, tag_count: usize) -> serde_json::Value {
    let mut users = Vec::with_capacity(user_count);
    for i in 0..user_count {
        users.push(json!({
            "id": i as i64,
            "name": format!("user-{}", i),
            "role": "member"
        }));
    }

    let mut tags = Vec::with_capacity(tag_count);
    for i in 0..tag_count {
        tags.push(json!({
            "id": format!("t{}", i),
            "value": format!("tag-{}", i)
        }));
    }

    json!({
        "users": users,
        "tags": tags
    })
}

fn build_input(record_count: usize, user_count: usize, tag_count: usize) -> String {
    let mut records = Vec::with_capacity(record_count);
    for i in 0..record_count {
        records.push(json!({
            "id": i as i64,
            "user_id": (i % user_count) as i64,
            "tag_id": format!("t{}", i % tag_count),
        }));
    }

    serde_json::to_string(&records).expect("failed to serialize input")
}

fn env_usize(name: &str, default: usize) -> usize {
    std::env::var(name)
        .ok()
        .and_then(|value| value.parse::<usize>().ok())
        .unwrap_or(default)
}