use std::env;
use std::fs;
use std::path::PathBuf;
use serde_json::Value;
fn main() {
let spec_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("openapi.json");
println!("cargo:rerun-if-changed={}", spec_path.display());
let spec_text = fs::read_to_string(&spec_path)
.unwrap_or_else(|e| panic!("read {}: {e}", spec_path.display()));
let mut spec: Value = serde_json::from_str(&spec_text).expect("parse pathbase-openapi.json");
downgrade_to_oas_30(&mut spec);
let spec: openapiv3::OpenAPI =
serde_json::from_value(spec).expect("downgraded spec doesn't match openapiv3 model");
let mut generator = progenitor::Generator::default();
let tokens = generator
.generate_tokens(&spec)
.expect("progenitor generation failed");
let ast = syn::parse2::<syn::File>(tokens).expect("parse generated tokens");
let formatted = prettyplease::unparse(&ast);
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR set by cargo"));
let out_file = out_dir.join("pathbase_client.rs");
fs::write(&out_file, formatted).unwrap_or_else(|e| panic!("write {}: {e}", out_file.display()));
}
fn downgrade_to_oas_30(value: &mut Value) {
if let Value::Object(map) = value
&& let Some(version) = map.get_mut("openapi")
&& version.as_str().is_some_and(|s| s.starts_with("3.1"))
{
*version = Value::String("3.0.3".to_string());
}
rewrite_nullable_types(value);
fill_empty_media_types(value);
}
fn fill_empty_media_types(value: &mut Value) {
match value {
Value::Object(map) => {
if let Some(Value::Object(content)) = map.get_mut("content") {
for media in content.values_mut() {
if let Value::Object(media_map) = media
&& !media_map.contains_key("schema")
{
let mut schema = serde_json::Map::new();
schema.insert("type".into(), Value::String("object".into()));
media_map.insert("schema".into(), Value::Object(schema));
}
}
}
for v in map.values_mut() {
fill_empty_media_types(v);
}
}
Value::Array(arr) => {
for v in arr.iter_mut() {
fill_empty_media_types(v);
}
}
_ => {}
}
}
fn rewrite_nullable_types(value: &mut Value) {
match value {
Value::Object(map) => {
if let Some(Value::Array(arr)) = map.get("type") {
let strings: Vec<String> = arr
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect();
if strings.len() == arr.len() {
let nullable = strings.iter().any(|s| s == "null");
let non_null: Vec<String> =
strings.into_iter().filter(|s| s != "null").collect();
if nullable && non_null.len() == 1 {
map.insert("type".into(), Value::String(non_null[0].clone()));
map.insert("nullable".into(), Value::Bool(true));
} else if non_null.len() == 1 {
map.insert("type".into(), Value::String(non_null[0].clone()));
}
}
}
for v in map.values_mut() {
rewrite_nullable_types(v);
}
}
Value::Array(arr) => {
for v in arr.iter_mut() {
rewrite_nullable_types(v);
}
}
_ => {}
}
}