use jsonschema::JSONSchema;
use klieo_core::error::ToolError;
pub fn validate_args(
schema: &serde_json::Value,
args: &serde_json::Value,
) -> Result<(), ToolError> {
let compiled = JSONSchema::compile(schema)
.map_err(|e| ToolError::Permanent(format!("invalid tool schema: {e}")))?;
let result = compiled.validate(args);
if let Err(errors) = result {
let messages: Vec<String> = errors.map(|e| format!("{e}")).collect();
return Err(ToolError::InvalidArgs(messages.join("; ")));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn schema() -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"query": { "type": "string" },
"limit": { "type": "integer", "minimum": 0 },
},
"required": ["query"],
"additionalProperties": false,
})
}
#[test]
fn valid_args_pass() {
let s = schema();
let args = serde_json::json!({ "query": "hello", "limit": 5 });
validate_args(&s, &args).unwrap();
}
#[test]
fn missing_required_field_rejected() {
let s = schema();
let args = serde_json::json!({ "limit": 5 });
let err = validate_args(&s, &args).unwrap_err();
assert!(matches!(err, ToolError::InvalidArgs(_)));
}
#[test]
fn wrong_type_rejected() {
let s = schema();
let args = serde_json::json!({ "query": 42 });
let err = validate_args(&s, &args).unwrap_err();
assert!(matches!(err, ToolError::InvalidArgs(_)));
}
#[test]
fn extra_field_rejected_when_additional_props_false() {
let s = schema();
let args = serde_json::json!({ "query": "hi", "junk": true });
let err = validate_args(&s, &args).unwrap_err();
assert!(matches!(err, ToolError::InvalidArgs(_)));
}
#[test]
fn invalid_schema_returns_permanent() {
let s = serde_json::json!({ "type": "not-a-real-type" });
let args = serde_json::json!({});
let err = validate_args(&s, &args).unwrap_err();
assert!(matches!(err, ToolError::Permanent(_)));
}
#[test]
fn out_of_range_integer_rejected() {
let s = schema();
let args = serde_json::json!({ "query": "hi", "limit": -1 });
let err = validate_args(&s, &args).unwrap_err();
assert!(matches!(err, ToolError::InvalidArgs(_)));
}
}