use serde_json::{Map, Value, json};
const UNSUPPORTED_KEYWORDS: &[&str] = &[
"minimum",
"maximum",
"exclusiveMinimum",
"exclusiveMaximum",
"multipleOf",
"minLength",
"maxLength",
"maxItems",
"uniqueItems",
"minProperties",
"maxProperties",
];
#[must_use]
pub(super) fn strict_input_schema(schema: &Value) -> Value {
let mut shaped = schema.clone();
shape(&mut shaped);
shaped
}
fn shape(value: &mut Value) {
let Some(object) = value.as_object_mut() else {
return;
};
for keyword in UNSUPPORTED_KEYWORDS {
object.remove(*keyword);
}
if object
.get("minItems")
.and_then(Value::as_u64)
.is_some_and(|n| n > 1)
{
object.remove("minItems");
}
lift_null_type(object);
if object.get("type").and_then(Value::as_str) == Some("object") {
object.insert("additionalProperties".to_owned(), Value::Bool(false));
}
for child in ["properties", "$defs", "definitions"] {
if let Some(Value::Object(children)) = object.get_mut(child) {
children.values_mut().for_each(shape);
}
}
if let Some(items) = object.get_mut("items") {
shape(items);
}
for keyword in ["anyOf", "oneOf", "allOf"] {
if let Some(Value::Array(variants)) = object.get_mut(keyword) {
variants.iter_mut().for_each(shape);
}
}
}
fn lift_null_type(object: &mut Map<String, Value>) {
let Some(Value::Array(types)) = object.get("type") else {
return;
};
let non_null: Vec<Value> = types
.iter()
.filter(|t| t.as_str() != Some("null"))
.cloned()
.collect();
if non_null.len() == types.len() {
return;
}
let mut branch = object.clone();
if let Some(Value::Array(values)) = branch.get_mut("enum") {
values.retain(|v| !v.is_null());
}
match non_null.len() {
0 => {
branch.remove("type");
},
1 => {
branch.insert(
"type".to_owned(),
non_null.into_iter().next().unwrap_or(Value::Null),
);
},
_ => {
branch.insert("type".to_owned(), Value::Array(non_null));
},
}
let mut shaped_branch = Value::Object(branch);
shape(&mut shaped_branch);
object.clear();
object.insert("anyOf".to_owned(), json!([shaped_branch, {"type": "null"}]));
}