use std::{env, fs, path::Path};
fn main() {
let workspace_schema = Path::new("../../schema/flow.v1.schema.json");
let local_schema = Path::new("schema/flow.v1.schema.json");
println!("cargo:rerun-if-changed=../../schema/flow.v1.schema.json");
println!("cargo:rerun-if-changed=schema/flow.v1.schema.json");
println!("cargo:rerun-if-changed=build.rs");
let raw = if workspace_schema.exists() {
let raw = fs::read_to_string(workspace_schema).expect("read schema");
if fs::read_to_string(local_schema).ok().as_deref() != Some(raw.as_str()) {
fs::create_dir_all("schema").expect("create crate-local schema dir");
fs::write(local_schema, &raw).expect("sync crate-local schema copy");
}
raw
} else {
fs::read_to_string(local_schema).expect("read crate-local schema copy")
};
let mut value: serde_json::Value = serde_json::from_str(&raw).expect("parse schema json");
rewrite_defs(&mut value);
inline_node_variants(&mut value);
let schema: schemars::schema::RootSchema =
serde_json::from_value(value).expect("schema into RootSchema");
let mut type_space =
typify::TypeSpace::new(typify::TypeSpaceSettings::default().with_struct_builder(false));
type_space
.add_root_schema(schema)
.expect("typify add_root_schema");
let tokens = type_space.to_stream();
let formatted = prettyplease::unparse(&syn::parse2::<syn::File>(tokens).expect("parse tokens"));
let out = Path::new(&env::var("OUT_DIR").unwrap()).join("flow_types.rs");
fs::write(out, formatted).expect("write generated types");
}
fn inline_node_variants(v: &mut serde_json::Value) {
let Some(defs) = v.get("definitions").and_then(|d| d.as_object()).cloned() else {
return;
};
let Some(one_of) = v
.get("definitions")
.and_then(|d| d.get("Node"))
.and_then(|n| n.get("oneOf"))
.and_then(|o| o.as_array())
.cloned()
else {
return;
};
let mut consumed: Vec<String> = Vec::new();
let mut inlined: Vec<serde_json::Value> = Vec::new();
for branch in &one_of {
match branch.get("$ref").and_then(|r| r.as_str()) {
Some(r) => {
let name = r
.strip_prefix("#/definitions/")
.expect("node variant ref shape");
let mut body = defs
.get(name)
.unwrap_or_else(|| panic!("missing node definition {name}"))
.clone();
rewrite_kind_const_to_enum(&mut body);
consumed.push(name.to_string());
inlined.push(body);
}
None => inlined.push(branch.clone()),
}
}
let defs_mut = v
.get_mut("definitions")
.and_then(|d| d.as_object_mut())
.expect("definitions object");
defs_mut
.get_mut("Node")
.and_then(|n| n.as_object_mut())
.expect("Node object")
.insert("oneOf".to_string(), serde_json::Value::Array(inlined));
for name in consumed {
defs_mut.remove(&name);
}
}
fn rewrite_kind_const_to_enum(body: &mut serde_json::Value) {
let Some(kind) = body
.get_mut("properties")
.and_then(|p| p.get_mut("kind"))
.and_then(|k| k.as_object_mut())
else {
return;
};
if !kind.contains_key("enum") {
if let Some(c) = kind.remove("const") {
kind.insert("enum".to_string(), serde_json::Value::Array(vec![c]));
}
}
}
fn rewrite_defs(v: &mut serde_json::Value) {
match v {
serde_json::Value::Object(map) => {
if let Some(defs) = map.remove("$defs") {
map.insert("definitions".to_string(), defs);
}
if let Some(serde_json::Value::String(r)) = map.get_mut("$ref") {
if let Some(rest) = r.strip_prefix("#/$defs/") {
*r = format!("#/definitions/{rest}");
}
}
for (_k, child) in map.iter_mut() {
rewrite_defs(child);
}
}
serde_json::Value::Array(items) => {
for item in items.iter_mut() {
rewrite_defs(item);
}
}
_ => {}
}
}