use std::{fs, path::Path};
use typify::{TypeSpace, TypeSpaceSettings};
const SCHEMA_URL: &'static str = "https://raw.githubusercontent.com/versa-protocol/schema";
const SCHEMA_PATH: &'static str = "data";
const SCHEMA_VERSION: &'static str = "1.11.0"; const SCHEMAS: [&'static str; 2] = ["receipt", "itinerary"];
const LTS_SCHEMA_VERSIONS: [&'static str; 5] = ["1.7.0", "1.8.0", "1.9.0", "1.10.0", "1.11.0"];
fn get_schema_definition_json(schema_name: &str, version: &str) -> Result<String, ()> {
let schema_url = format!(
"{}/{}/{}/{}.schema.json",
SCHEMA_URL, version, SCHEMA_PATH, schema_name
);
match ureq::get(&schema_url).call() {
Ok(mut res) => {
let response_status = res.status();
if response_status != ureq::http::StatusCode::OK {
println!(
"Error fetching schema version {}: {}",
version, response_status
);
return Err(());
}
return match res.body_mut().read_to_string() {
Ok(text) => Ok(text),
Err(e) => {
println!("Error fetching schema version {}: {:?}", version, e);
Err(())
}
};
}
Err(e) => {
println!("Error fetching schema version {}: {:?}", version, e);
return Err(());
}
}
}
fn generate_schema_bindings(schema_name: &str) {
let Ok(content) = get_schema_definition_json(schema_name, SCHEMA_VERSION) else {
return;
};
let schema = serde_json::from_str::<schemars::schema::RootSchema>(&content).unwrap();
let mut type_space = TypeSpace::new(TypeSpaceSettings::default().with_struct_builder(true));
type_space.add_root_schema(schema).unwrap();
let contents = prettyplease::unparse(&syn::parse2::<syn::File>(type_space.to_stream()).unwrap())
.replace("chrono::naive::NaiveDate", "String");
let mut out_file = Path::new("src/schema").to_path_buf();
out_file.push(format!("{}.rs", schema_name));
fs::write(out_file, contents).unwrap();
}
fn strip_additional_properties(value: &mut serde_json::Value) {
match value {
serde_json::Value::Object(map) => {
map.remove("additionalProperties");
for v in map.values_mut() {
strip_additional_properties(v);
}
}
serde_json::Value::Array(arr) => {
for v in arr {
strip_additional_properties(v);
}
}
_ => {}
}
}
fn generate_tolerant_schema_bindings(schema_name: &str) {
let Ok(content) = get_schema_definition_json(schema_name, SCHEMA_VERSION) else {
return;
};
let mut val = serde_json::from_str::<serde_json::Value>(&content).unwrap();
strip_additional_properties(&mut val);
let stripped_schema = serde_json::to_string(&val).unwrap();
let schema = serde_json::from_str::<schemars::schema::RootSchema>(&stripped_schema).unwrap();
let mut type_space = TypeSpace::new(TypeSpaceSettings::default().with_struct_builder(true));
type_space.add_root_schema(schema).unwrap();
let contents = prettyplease::unparse(&syn::parse2::<syn::File>(type_space.to_stream()).unwrap())
.replace("chrono::naive::NaiveDate", "String");
let mut out_file = Path::new("src/schema").to_path_buf();
out_file.push(format!("{}_unstrict.rs", schema_name));
fs::write(out_file, contents).unwrap();
}
fn bundle_schema_definition(schema: &str, version: &str) {
let Ok(content) = get_schema_definition_json(schema, version) else {
return;
};
let safe_version = format!("{}", version.replace('.', "_"));
let safe_version_name = format!("v{}", safe_version);
let mut out_file = Path::new("src/schema/json").to_path_buf();
out_file.push(format!("{}_{}.json", schema, safe_version_name));
fs::write(out_file, content).unwrap();
println!(
"Bundled schema definition for {} version {}",
schema, version
);
}
fn bundle_schema_definitions(schema: &str) {
for version in LTS_SCHEMA_VERSIONS {
bundle_schema_definition(schema, version);
}
}
fn output_current_schema_version() {
let mut out_file = Path::new("src/schema").to_path_buf();
out_file.push("current.rs");
fs::write(
out_file,
format!(
"pub const SCHEMA_VERSION: &str = \"{}\";
",
SCHEMA_VERSION
),
)
.unwrap();
}
fn main() {
let json_dir = Path::new("src/schema/json");
if json_dir.exists() {
for entry in fs::read_dir(json_dir).unwrap() {
let entry = entry.unwrap();
if entry
.path()
.extension()
.map(|s| s == "json")
.unwrap_or(false)
{
fs::remove_file(entry.path()).unwrap();
}
}
} else {
fs::create_dir_all(json_dir).unwrap();
}
output_current_schema_version();
for schema in SCHEMAS.iter() {
generate_schema_bindings(schema);
generate_tolerant_schema_bindings(schema);
bundle_schema_definitions(schema);
}
}