use anyhow::{anyhow, Result};
use serde_json::Value;
pub fn validate(schema: &Value, params: &Value) -> Result<()> {
let validator =
jsonschema::validator_for(schema).map_err(|e| anyhow!("invalid params_schema: {e}"))?;
let errors: Vec<String> = validator
.iter_errors(params)
.take(3)
.map(|e| format!("{}: {}", e.instance_path, e))
.collect();
if errors.is_empty() {
Ok(())
} else {
Err(anyhow!(errors.join("; ")))
}
}
pub fn validate_against_stored(schema_text: &str, params: &Value) -> Result<()> {
let schema: Value = serde_json::from_str(schema_text)
.map_err(|e| anyhow!("stored params_schema is not valid JSON: {e}"))?;
validate(&schema, params)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn schema_required_int() -> Value {
json!({
"type": "object",
"required": ["count"],
"properties": { "count": { "type": "integer", "minimum": 0 } }
})
}
#[test]
fn validates_conforming_params() {
validate(&schema_required_int(), &json!({"count": 3})).unwrap();
}
#[test]
fn rejects_missing_required_key() {
let err = validate(&schema_required_int(), &json!({})).unwrap_err();
assert!(err.to_string().to_lowercase().contains("count"));
}
#[test]
fn rejects_wrong_type() {
let err = validate(&schema_required_int(), &json!({"count": "five"})).unwrap_err();
assert!(
err.to_string().to_lowercase().contains("integer")
|| err.to_string().to_lowercase().contains("type")
);
}
#[test]
fn rejects_below_minimum() {
let err = validate(&schema_required_int(), &json!({"count": -1})).unwrap_err();
assert!(
err.to_string().to_lowercase().contains("minimum") || err.to_string().contains("-1")
);
}
#[test]
fn rejects_invalid_schema() {
let bad_schema = json!({"type": "not-a-real-type"});
let err = validate(&bad_schema, &json!({})).unwrap_err();
assert!(err.to_string().contains("invalid params_schema"));
}
#[test]
fn validate_against_stored_parses_text() {
let text = serde_json::to_string(&schema_required_int()).unwrap();
validate_against_stored(&text, &json!({"count": 0})).unwrap();
let err = validate_against_stored(&text, &json!({})).unwrap_err();
assert!(err.to_string().to_lowercase().contains("count"));
}
#[test]
fn validate_against_stored_rejects_garbage_json() {
let err = validate_against_stored("{not json}", &json!({})).unwrap_err();
assert!(err.to_string().contains("not valid JSON"));
}
}