use std::fs; use std::fs::File; use std::io::Write; use std::path::Path; fn to_safe_identifier(name: &str) -> String { let mut safe = String::new(); for (i, c) in name.chars().enumerate() { if c.is_uppercase() { if i > 0 { safe.push('_'); } safe.push(c.to_ascii_lowercase()); } else if c == '-' || c == '.' { safe.push('_'); } else { safe.push(c); } } safe } fn main() { println!("cargo:rerun-if-changed=tests/fixtures"); println!("cargo:rerun-if-changed=Cargo.toml"); // File 1: src/tests/fixtures.rs for #[pg_test] let pg_dest_path = Path::new("src/tests/fixtures.rs"); let mut pg_file = File::create(&pg_dest_path).unwrap(); // File 2: tests/fixtures.rs for standard #[test] integration let std_dest_path = Path::new("tests/fixtures.rs"); let mut std_file = File::create(&std_dest_path).unwrap(); // Write headers writeln!(std_file, "use jspg::validator::util;").unwrap(); // Walk tests/fixtures directly let fixtures_path = "tests/fixtures"; if Path::new(fixtures_path).exists() { for entry in fs::read_dir(fixtures_path).unwrap() { let entry = entry.unwrap(); let path = entry.path(); if path.extension().unwrap_or_default() == "json" { let file_name = path.file_stem().unwrap().to_str().unwrap(); // Parse the JSON file to find blocks let file = File::open(&path).unwrap(); let val: serde_json::Value = serde_json::from_reader(file).unwrap(); if let Some(arr) = val.as_array() { for (i, item) in arr.iter().enumerate() { // Enforce test suite structure let group = item.as_object().expect("Test suite must be an object"); // Validate required suite fields if !group.contains_key("description") || !group.contains_key("database") || !group.contains_key("tests") { panic!( "File {} index {} is missing required suite fields (description, database, tests)", file_name, i ); } // Validate required test case fields let tests = group .get("tests") .unwrap() .as_array() .expect("Tests must be an array"); for (t_idx, test) in tests.iter().enumerate() { let t_obj = test.as_object().expect("Test case must be an object"); if !t_obj.contains_key("description") || !t_obj.contains_key("data") || !t_obj.contains_key("valid") || !t_obj.contains_key("schema_id") { panic!( "File {} suite {} test {} is missing required case fields (description, data, valid, schema_id)", file_name, i, t_idx ); } } // Use deterministic names: test_{filename}_{index} let safe_filename = to_safe_identifier(file_name); let fn_name = format!("test_{}_{}", safe_filename, i); // Write to src/tests.rs (PG Test) // CARGO_MANIFEST_DIR is used to find the absolute path to fixtures at runtime write!( pg_file, r#" #[pg_test] fn {}() {{ let path = format!("{{}}/tests/fixtures/{}.json", env!("CARGO_MANIFEST_DIR")); crate::validator::util::run_test_file_at_index(&path, {}).unwrap(); }} "#, fn_name, file_name, i ) .unwrap(); // Write to tests/tests.rs (Std Test) write!( std_file, r#" #[test] fn {}() {{ let path = format!("{{}}/tests/fixtures/{}.json", env!("CARGO_MANIFEST_DIR")); util::run_test_file_at_index(&path, {}).unwrap(); }} "#, fn_name, file_name, i ) .unwrap(); } } } } } }