use schemars::generate::SchemaSettings;
use serde_json::{Map, Value};
use crate::FlowIR;
pub const FLOW_IR_SCHEMA_ID: &str = "urn:pointlock:schema:ir:v0.1:flow-ir";
pub fn flow_ir_schema() -> Value {
let settings = SchemaSettings::draft2020_12();
let mut generator = settings.into_generator();
let schema = generator.root_schema_for::<FlowIR>();
let mut doc = serde_json::to_value(&schema).expect("schema serializes to JSON");
strip_option_null(&mut doc);
if let Value::Object(root) = &mut doc {
root.insert(
"$id".to_owned(),
Value::String(FLOW_IR_SCHEMA_ID.to_owned()),
);
root.insert("title".to_owned(), Value::String("FlowIR".to_owned()));
}
doc
}
fn strip_option_null(value: &mut Value) {
match value {
Value::Object(obj) => {
strip_null_from_type(obj);
collapse_nullable_any_of(obj);
for (_, v) in obj.iter_mut() {
strip_option_null(v);
}
}
Value::Array(items) => {
for item in items {
strip_option_null(item);
}
}
_ => {}
}
}
fn strip_null_from_type(obj: &mut Map<String, Value>) {
let Some(Value::Array(types)) = obj.get_mut("type") else {
return;
};
types.retain(|t| t != "null");
if types.len() == 1 {
let only = types[0].clone();
obj.insert("type".to_owned(), only);
}
}
fn collapse_nullable_any_of(obj: &mut Map<String, Value>) {
let is_null_schema = |v: &Value| matches!(v, Value::Object(o) if o.get("type") == Some(&Value::String("null".to_owned())) && o.len() == 1);
let Some(Value::Array(branches)) = obj.get("anyOf") else {
return;
};
if branches.len() != 2 || !branches.iter().any(is_null_schema) {
return;
}
let non_null = branches
.iter()
.find(|b| !is_null_schema(b))
.cloned()
.unwrap_or(Value::Bool(true));
obj.remove("anyOf");
if let Value::Object(inner) = non_null {
for (k, v) in inner {
obj.insert(k, v);
}
}
}