use serde_json::Value;
pub const TRANSFORM_ID: &str = "schema_compaction";
pub const TRANSFORM_VERSION: &str = "1.0.0";
#[derive(Debug, thiserror::Error)]
pub enum SchemaCompactionError {
#[error("invalid json: {0}")]
Invalid(#[from] serde_json::Error),
}
pub fn compact_schema(input: &[u8], max_examples: usize) -> Result<Vec<u8>, SchemaCompactionError> {
let mut value: Value = serde_json::from_slice(input)?;
shorten_examples(&mut value, max_examples);
let bytes = serde_json::to_vec(&value)?;
Ok(bytes)
}
fn shorten_examples(value: &mut Value, max_examples: usize) {
match value {
Value::Object(map) => {
if let Some(Value::Array(examples)) = map.get_mut("examples") {
examples.truncate(max_examples);
}
for v in map.values_mut() {
shorten_examples(v, max_examples);
}
}
Value::Array(arr) => {
for v in arr.iter_mut() {
shorten_examples(v, max_examples);
}
}
Value::String(_) | Value::Number(_) | Value::Bool(_) | Value::Null => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn description_field_preserved_byte_for_byte() {
let input = json!({
"name": "get_weather",
"description": "Fetches the current weather conditions for a named city or postal code.",
"parameters": {
"type": "object",
"properties": {
"location": { "type": "string" }
}
}
});
let bytes = serde_json::to_vec(&input).unwrap();
let out = compact_schema(&bytes, 3).unwrap();
let parsed: Value = serde_json::from_slice(&out).unwrap();
assert_eq!(
parsed["description"],
json!("Fetches the current weather conditions for a named city or postal code.")
);
}
#[test]
fn required_array_preserved() {
let input = json!({
"parameters": {
"type": "object",
"properties": {
"a": { "type": "string" },
"b": { "type": "number" }
}
},
"required": ["a", "b"]
});
let bytes = serde_json::to_vec(&input).unwrap();
let out = compact_schema(&bytes, 2).unwrap();
let parsed: Value = serde_json::from_slice(&out).unwrap();
assert_eq!(parsed["required"], json!(["a", "b"]));
}
#[test]
fn enum_array_preserved() {
let input = json!({
"parameters": {
"properties": {
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
}
}
});
let bytes = serde_json::to_vec(&input).unwrap();
let out = compact_schema(&bytes, 1).unwrap();
let parsed: Value = serde_json::from_slice(&out).unwrap();
assert_eq!(
parsed["parameters"]["properties"]["unit"]["enum"],
json!(["celsius", "fahrenheit"])
);
}
#[test]
fn type_field_preserved() {
let input = json!({
"parameters": {
"type": "object",
"properties": {}
}
});
let bytes = serde_json::to_vec(&input).unwrap();
let out = compact_schema(&bytes, 1).unwrap();
let parsed: Value = serde_json::from_slice(&out).unwrap();
assert_eq!(parsed["parameters"]["type"], json!("object"));
}
#[test]
fn default_field_preserved() {
let input = json!({
"parameters": {
"properties": {
"count": { "type": "integer", "default": 10 }
}
}
});
let bytes = serde_json::to_vec(&input).unwrap();
let out = compact_schema(&bytes, 1).unwrap();
let parsed: Value = serde_json::from_slice(&out).unwrap();
assert_eq!(
parsed["parameters"]["properties"]["count"]["default"],
json!(10)
);
}
#[test]
fn examples_array_shortened_to_max_examples_keeping_first_elements() {
let input = json!({
"parameters": {
"examples": ["one", "two", "three", "four", "five"]
}
});
let bytes = serde_json::to_vec(&input).unwrap();
let out = compact_schema(&bytes, 1).unwrap();
let parsed: Value = serde_json::from_slice(&out).unwrap();
assert_eq!(parsed["parameters"]["examples"], json!(["one"]));
}
#[test]
fn examples_array_shorter_than_max_is_left_unchanged() {
let input = json!({
"examples": ["only-one"]
});
let bytes = serde_json::to_vec(&input).unwrap();
let out = compact_schema(&bytes, 5).unwrap();
let parsed: Value = serde_json::from_slice(&out).unwrap();
assert_eq!(parsed["examples"], json!(["only-one"]));
}
#[test]
fn tool_name_field_preserved() {
let input = json!({
"name": "search_flights",
"parameters": {
"type": "object",
"properties": {}
}
});
let bytes = serde_json::to_vec(&input).unwrap();
let out = compact_schema(&bytes, 1).unwrap();
let parsed: Value = serde_json::from_slice(&out).unwrap();
assert_eq!(parsed["name"], json!("search_flights"));
}
#[test]
fn invalid_json_input_returns_error() {
let result = compact_schema(b"{not json", 1);
assert!(matches!(result, Err(SchemaCompactionError::Invalid(_))));
}
#[test]
fn document_with_no_examples_key_round_trips_without_panic() {
let input = json!({
"name": "lookup_stock_price",
"description": "Looks up the latest known stock price for a given ticker symbol.",
"parameters": {
"type": "object",
"properties": {
"ticker": { "type": "string" }
},
"required": ["ticker"]
}
});
let bytes = serde_json::to_vec(&input).unwrap();
let out = compact_schema(&bytes, 3).unwrap();
let parsed: Value = serde_json::from_slice(&out).unwrap();
assert_eq!(parsed, input);
}
}