use serde_json::{to_string, Number, Value};
use std::collections::HashSet;
use std::env;
use std::fs::read_to_string;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::str::FromStr;
use yaml_rust::{Yaml, YamlLoader};
fn main() {
let dev_path = std::path::Path::new("../specification.yml");
let crate_release_path = std::path::Path::new("specification.yml");
let spec_path = if dev_path.exists() {
dev_path
} else {
crate_release_path
};
println!("cargo:rerun-if-changed={}", spec_path.to_string_lossy());
let spec = read_to_string(spec_path).unwrap();
let spec = YamlLoader::load_from_str(&spec).unwrap();
let out_dir = env::var("OUT_DIR").unwrap();
let test_path = Path::new(&out_dir).join("test_spec.rs");
let mut test_file = File::create(&test_path).unwrap();
writeln!(test_file, "use serde_json::Value;").unwrap();
writeln!(test_file, "use json_e::{{render, use_test_now}};").unwrap();
let mut section = String::from("unknown");
let section_key = Yaml::String("section".into());
let title_key = Yaml::String("title".into());
let context_key = Yaml::String("context".into());
let template_key = Yaml::String("template".into());
let result_key = Yaml::String("result".into());
let error_key = Yaml::String("error".into());
let mut test_names = HashSet::new();
for item in spec {
if let Yaml::Hash(ref h) = item {
if let Some(v) = h.get(§ion_key) {
section = String::from(v.as_str().unwrap());
continue;
}
let title = h.get(&title_key).unwrap().as_str().unwrap();
let context = h.get(&context_key).unwrap();
let template = h.get(&template_key).unwrap();
let result = h.get(&result_key);
let error = h.get(&error_key);
write_test(
&mut test_file,
&mut test_names,
§ion,
title,
context,
template,
result,
error,
);
} else {
panic!("YAML sub-document is not an object: {:?}", item);
}
}
}
fn to_json(y: &Yaml) -> Value {
match y {
&Yaml::Real(ref v) => Value::Number(Number::from_str(v).unwrap()),
&Yaml::Integer(v) => Value::Number(v.into()),
&Yaml::String(ref v) => Value::String(v.into()),
&Yaml::Boolean(ref v) => Value::Bool(*v),
&Yaml::Array(ref v) => Value::Array(v.iter().map(|y| to_json(y)).collect()),
&Yaml::Hash(ref v) => Value::Object(
v.iter()
.map(|(k, v)| (k.as_str().unwrap().to_string(), to_json(v)))
.collect(),
),
&Yaml::Null => Value::Null,
&Yaml::Alias(_) => todo!(),
&Yaml::BadValue => todo!(),
}
}
fn to_json_str(y: &Yaml) -> String {
to_string(&to_json(y)).unwrap()
}
fn write_test(
test_file: &mut File,
test_names: &mut HashSet<String>,
section: &str,
title: &str,
context: &Yaml,
template: &Yaml,
result: Option<&Yaml>,
error: Option<&Yaml>,
) {
let mut test_name: String = format!("{}_{}", section, title)
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
.collect();
while test_names.contains(&test_name) {
test_name.push('_');
}
test_names.insert(test_name.clone());
let context = to_json_str(context);
let template = to_json_str(template);
write!(
test_file,
r##"
#[test]
fn {test_name}() {{
use_test_now();
println!("{{}} - {{}}", {section:?}, {title:?});
let context: Value = serde_json::from_str(r#"{context}"#).unwrap();
let template: Value = serde_json::from_str(r#"{template}"#).unwrap();
"##,
test_name = test_name,
section = section,
title = title,
context = context,
template = template
)
.unwrap();
if let Some(result) = result {
let result = to_json_str(result);
write!(
test_file,
r##"
let result: Value = serde_json::from_str(r#"{result}"#).unwrap();
assert_eq!(render(&template, &context).unwrap(), result);
}}
"##,
result = result
)
.unwrap();
} else if let Some(_error) = error {
write!(
test_file,
r##"
assert!(render(&template, &context).is_err());
}}
"##
)
.unwrap();
} else {
panic!("test case with neither result not error!");
}
}