use inflector::Inflector;
use serde::{Deserialize, Serialize};
use std::{error::Error, ffi::OsStr, fs, path::PathBuf, process::Command};
schemafy::schemafy!(
root: TestSchema
"tests/JSON-Schema-Test-Suite/test-schema.json"
);
fn main() -> Result<(), Box<dyn Error>> {
let test_suite_dir = PathBuf::from("tests/test_suite");
let schemas_dir = test_suite_dir.join("schemas");
if test_suite_dir.exists() {
fs::remove_dir_all("tests/test_suite")?;
}
fs::create_dir(&test_suite_dir)?;
fs::create_dir(&schemas_dir)?;
let mut test_modules = vec![];
let mut blacklist_count = 0;
for path in fs::read_dir("tests/JSON-Schema-Test-Suite/tests/draft4")?
.map(|entry| entry.unwrap().path())
.filter(|path| path.extension() == Some(OsStr::new("json")))
{
let buffer = fs::read_to_string(&path)?;
let test_schema: TestSchema = serde_json::from_str(&buffer)?;
println!("{} ==> {} tests", path.display(), test_schema.len());
let module_name = path.file_stem().unwrap().to_str().unwrap().to_snake_case();
let mut test_file: String = format!(
r#"//! Automatically generated from {}
"#,
path.display()
);
let mut is_module_empty = true;
for (i, test_group) in test_schema.iter().enumerate() {
if is_blacklisted(&module_name, i) {
blacklist_count += 1;
println!(" !! skipping test group: {}", test_group.description);
continue;
}
is_module_empty = false;
let schema_name = format!("{}_{}.json", module_name, i);
let schema = serde_json::to_string(&test_group.schema)?;
fs::write(schemas_dir.join(&schema_name), schema)?;
let schema = schemafy_lib::Generator::builder()
.with_root_name_str("Schema")
.with_input_file(&schemas_dir.join(schema_name))
.build()
.generate();
test_file.push_str(&format!(
r#"
mod _{}_{} {{
#[allow(unused_imports)]
use serde::{{Deserialize, Serialize}};
{}
"#,
i,
test_group.description.to_snake_case(),
&schema.to_string()
));
for test in &test_group.tests {
let test_name = {
let root = test.description.to_snake_case();
let prefix = if root.chars().next().unwrap().is_numeric() {
"_"
} else {
""
};
format!("{}{}", prefix, root)
};
let assertion = if test.valid {
"let _: Schema = serde_json::from_str(data).unwrap();"
} else {
"assert!(serde_json::from_str::<Schema>(data).is_err());"
};
test_file.push_str(&format!(
r##"
#[test]
fn r#{}() {{
let data = r#"{}"#;
{}
}}
"##,
test_name, test.data, assertion
));
}
test_file.push_str("}\n");
}
if is_module_empty {
continue;
}
let test_path = test_suite_dir.join(format!("{}.rs", module_name));
fs::write(&test_path, test_file)?;
Command::new("rustfmt").arg(test_path).output()?;
test_modules.push(module_name);
}
let mut tests: String = r#"//! Automatically generated
"#
.into();
for module in &test_modules {
tests.push_str(&format!("mod r#{};\n", module));
}
fs::write(test_suite_dir.join("mod.rs"), tests)?;
if blacklist_count > 0 {
println!("\nSkipped {} test schemas\n", blacklist_count);
}
Ok(())
}
fn is_blacklisted(test_group: &str, index: usize) -> bool {
let blacklisted_indices: &[usize] = match test_group {
"additional_items" => &[0, 2],
"additional_properties" => &[0, 1, 2, 3, 5],
"all_of" => &[1, 2, 5, 6],
"any_of" => &[0, 1, 2, 4],
"definitions" => &[0, 1],
"dependencies" => &[0, 1, 2, 3],
"enum" => &[0, 1, 3, 4, 5, 6, 7],
"items" => &[0, 1, 2],
"max_items" => &[0],
"max_length" => &[0],
"max_properties" => &[0],
"maximum" => &[0, 1, 2],
"min_items" => &[0],
"min_length" => &[0],
"min_properties" => &[0],
"minimum" => &[0, 1, 2, 3],
"multiple_of" => &[0, 1, 2],
"not" => &[0, 1, 2, 3],
"one_of" => &[0, 1, 2, 3, 4],
"pattern" => &[0],
"pattern_properties" => &[0, 1, 2],
"properties" => &[0, 1, 2],
"ref" => &[0, 1, 2, 3, 6, 8, 9, 10, 11, 12],
"ref_remote" => &[0, 1, 2, 3, 4, 5, 6],
"required" => &[0, 2],
"type" => &[6, 7, 9, 10],
"unique_items" => &[0, 1, 2],
_ => &[],
};
blacklisted_indices.contains(&index)
}