//! The Rust adapter for the shared golden vectors — the third implementation alongside //! `tests/adapters/js_adapter.mjs` and `tests/adapters/py_adapter.py`. Reads the same //! `tests/fixtures/track-format.json` and asserts each case's normalized form + round-trip. use serde_json::{json, Value}; use track_format::{parse, serialize, End, Track}; /// Build the neutral normalized structure (docs/track-format.md §5) from a parsed Track. fn norm(t: &Track) -> Value { json!({ "bpm": t.bpm, "bars": t.bars, "volume": t.volume, "countMs": t.count_ms, "ramp": t.ramp.as_ref().map(|r| json!({"start": r.start, "amt": r.amt, "every": r.every})), "trainer": t.trainer.as_ref().map(|x| json!({"play": x.play, "mute": x.mute})), "rep": match t.end { None => Value::Null, Some(_) => json!(t.rep.unwrap_or(1)) }, "end": match &t.end { None => Value::Null, Some(End::Stop) => json!("stop"), Some(End::Goto(n)) => json!(n), }, "lanes": t.lanes.iter().map(|l| json!({ "sound": l.sound, "groups": l.groups, "sub": l.sub, "swing": l.swing, "poly": l.poly, "mute": l.mute, "gainDb": l.gain_db, "levels": l.levels })).collect::>(), }) } /// Deep-equal that treats all JSON numbers as f64 (so 0 == 0.0 across the int/float boundary). fn json_eq(a: &Value, b: &Value) -> bool { match (a, b) { (Value::Number(x), Value::Number(y)) => x.as_f64() == y.as_f64(), (Value::Array(x), Value::Array(y)) => { x.len() == y.len() && x.iter().zip(y).all(|(p, q)| json_eq(p, q)) } (Value::Object(x), Value::Object(y)) => { x.len() == y.len() && x.iter().all(|(k, v)| y.get(k).map_or(false, |w| json_eq(v, w))) } _ => a == b, } } fn fixtures() -> Value { let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/fixtures/track-format.json"); let raw = std::fs::read_to_string(path).expect("read fixtures json"); serde_json::from_str(&raw).expect("parse fixtures json") } #[test] fn conformance() { let doc = fixtures(); let mut fails = 0; for case in doc["cases"].as_array().unwrap() { let id = case["id"].as_str().unwrap_or("?"); let patch = case["in"].as_str().unwrap_or(""); let got = norm(&parse(patch)); if !json_eq(&case["norm"], &got) { fails += 1; eprintln!("FAIL {id}\n expected: {}\n got: {got}", case["norm"]); } } assert_eq!(fails, 0, "{fails} conformance mismatch(es)"); } #[test] fn idempotent() { let doc = fixtures(); for case in doc["cases"].as_array().unwrap() { let patch = case["in"].as_str().unwrap_or(""); let c1 = serialize(&parse(patch)); let c2 = serialize(&parse(&c1)); assert_eq!(c1, c2, "non-idempotent serialize for {}", case["id"]); } }