use schemars::Schema;
use serde_json::{Map, Value};
pub(crate) fn strip_int_formats(schema: &mut Schema) {
if let Some(object) = schema.as_object_mut() {
strip_in_map(object);
}
}
fn strip_in_map(map: &mut Map<String, Value>) {
let drop_format = matches!(map.get("format"), Some(Value::String(f)) if is_rust_int_format(f));
if drop_format {
map.remove("format");
}
for value in map.values_mut() {
strip_in_value(value);
}
}
fn strip_in_value(value: &mut Value) {
match value {
Value::Object(map) => strip_in_map(map),
Value::Array(items) => items.iter_mut().for_each(strip_in_value),
_ => {}
}
}
#[cfg(feature = "mcp")]
pub(crate) fn widen_id_properties(map: &mut Map<String, Value>, keys: &[&str]) {
if let Some(Value::Object(properties)) = map.get_mut("properties") {
for (name, subschema) in properties.iter_mut() {
if keys.contains(&name.as_str()) {
widen_id_schema(subschema);
}
}
}
for value in map.values_mut() {
widen_in_value(value, keys);
}
}
#[cfg(feature = "mcp")]
fn widen_in_value(value: &mut Value, keys: &[&str]) {
match value {
Value::Object(map) => widen_id_properties(map, keys),
Value::Array(items) => items.iter_mut().for_each(|item| widen_in_value(item, keys)),
_ => {}
}
}
#[cfg(feature = "mcp")]
fn widen_id_schema(schema: &mut Value) {
let Value::Object(map) = schema else {
return;
};
match map.get("type").cloned() {
Some(Value::String(kind)) if kind == "integer" => {
map.insert(
"type".to_owned(),
Value::Array(vec![
Value::String("integer".to_owned()),
Value::String("string".to_owned()),
]),
);
}
Some(Value::String(kind)) if kind == "array" => {
if let Some(items) = map.get_mut("items") {
widen_id_schema(items);
}
}
Some(Value::Array(mut kinds)) => {
let has_integer = kinds.iter().any(|kind| kind == "integer");
let has_string = kinds.iter().any(|kind| kind == "string");
if has_integer && !has_string {
let after = kinds
.iter()
.position(|kind| kind == "integer")
.map_or(kinds.len(), |position| position + 1);
kinds.insert(after, Value::String("string".to_owned()));
map.insert("type".to_owned(), Value::Array(kinds));
}
}
_ => {}
}
}
#[cfg(feature = "mcp")]
pub(crate) fn inline_ref_only_properties(map: &mut Map<String, Value>) {
let Some(Value::Object(defs)) = map.get("$defs").cloned() else {
return;
};
let Some(Value::Object(properties)) = map.get_mut("properties") else {
return;
};
for subschema in properties.values_mut() {
let Value::Object(prop) = subschema else {
continue;
};
if prop.contains_key("type") {
continue;
}
let Some(name) = ref_only_target(prop) else {
continue;
};
let Some(Value::Object(definition)) = defs.get(&name) else {
continue;
};
let mut merged = definition.clone();
for (key, value) in prop.iter() {
if key != "$ref" && key != "allOf" {
merged.insert(key.clone(), value.clone());
}
}
*prop = merged;
}
}
#[cfg(feature = "mcp")]
fn ref_only_target(prop: &Map<String, Value>) -> Option<String> {
let reference = match (prop.get("$ref"), prop.get("allOf")) {
(Some(Value::String(r)), _) => r.clone(),
(None, Some(Value::Array(items))) if items.len() == 1 => match &items[0] {
Value::Object(inner) => match inner.get("$ref") {
Some(Value::String(r)) => r.clone(),
_ => return None,
},
_ => return None,
},
_ => return None,
};
reference.strip_prefix("#/$defs/").map(str::to_owned)
}
fn is_rust_int_format(format: &str) -> bool {
matches!(
format,
"uint"
| "uint8"
| "uint16"
| "uint32"
| "uint64"
| "uint128"
| "int"
| "int8"
| "int16"
| "int32"
| "int64"
| "int128"
)
}
#[cfg(test)]
#[path = "schema_tests.rs"]
mod tests;