use anyhow::{Context, bail};
use serde_json::Value;
pub fn validate_data_against_schema(data: &Value, schema: &Value) -> anyhow::Result<()> {
if schema.is_null() {
return Ok(());
}
let validator = jsonschema::validator_for(schema).context("invalid JSON Schema")?;
let errors: Vec<String> = validator
.iter_errors(data)
.map(|e| {
let path = e.instance_path.to_string();
if path.is_empty() {
e.to_string()
} else {
format!("{}: {}", path, e)
}
})
.collect();
if errors.is_empty() {
Ok(())
} else {
bail!("data validation failed:\n - {}", errors.join("\n - "));
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_valid_data() {
let schema = json!({
"type": "object",
"required": ["name"],
"properties": { "name": { "type": "string" } }
});
let data = json!({"name": "Alice"});
assert!(validate_data_against_schema(&data, &schema).is_ok());
}
#[test]
fn test_missing_required_field() {
let schema = json!({
"type": "object",
"required": ["name"],
"properties": { "name": { "type": "string" } }
});
let data = json!({});
assert!(validate_data_against_schema(&data, &schema).is_err());
}
#[test]
fn test_wrong_type() {
let schema = json!({
"type": "object",
"properties": { "age": { "type": "integer" } }
});
let data = json!({"age": "not a number"});
assert!(validate_data_against_schema(&data, &schema).is_err());
}
#[test]
fn test_null_schema_skips_validation() {
let data = json!({"anything": "goes"});
assert!(validate_data_against_schema(&data, &Value::Null).is_ok());
}
}