use serde_json::Value;
use crate::error::ValidationError;
pub(crate) const DRAFT_2020_12_KEYWORDS: &[&str] = &[
"$schema",
"$id",
"$ref",
"$defs",
"$anchor",
"$dynamicAnchor",
"$dynamicRef",
"$vocabulary",
"$comment",
"allOf",
"anyOf",
"oneOf",
"not",
"if",
"then",
"else",
"dependentSchemas",
"prefixItems",
"items",
"contains",
"properties",
"patternProperties",
"additionalProperties",
"propertyNames",
"type",
"enum",
"const",
"multipleOf",
"maximum",
"exclusiveMaximum",
"minimum",
"exclusiveMinimum",
"maxLength",
"minLength",
"pattern",
"maxItems",
"minItems",
"uniqueItems",
"maxContains",
"minContains",
"maxProperties",
"minProperties",
"required",
"dependentRequired",
"format",
"contentEncoding",
"contentMediaType",
"contentSchema",
"title",
"description",
"default",
"deprecated",
"readOnly",
"writeOnly",
"examples",
"unevaluatedItems",
"unevaluatedProperties",
];
pub(crate) fn is_known_draft_2020_12_keyword(k: &str) -> bool {
DRAFT_2020_12_KEYWORDS.contains(&k)
}
pub(crate) fn strict_keyword_check(schema: &Value, pointer: &str) -> Result<(), ValidationError> {
match schema {
Value::Object(map) => {
for (k, v) in map {
match k.as_str() {
"properties" | "patternProperties" | "$defs" | "dependentSchemas" => {
if let Value::Object(inner) = v {
for (name, sub) in inner {
let next = format!("{pointer}/{k}/{name}");
strict_keyword_check(sub, &next)?;
}
}
continue;
}
_ => {}
}
if !is_known_draft_2020_12_keyword(k) {
return Err(ValidationError::UnsupportedKeyword { keyword: k.clone() });
}
let next = format!("{pointer}/{k}");
match v {
Value::Object(_) => strict_keyword_check(v, &next)?,
Value::Array(items) => {
for (i, item) in items.iter().enumerate() {
let p = format!("{next}/{i}");
if item.is_object() {
strict_keyword_check(item, &p)?;
}
}
}
_ => {}
}
}
Ok(())
}
_ => Ok(()),
}
}
pub(crate) fn compile_schema(schema: &Value) -> Result<jsonschema::Validator, ValidationError> {
strict_keyword_check(schema, "#")?;
jsonschema::draft202012::new(schema).map_err(|e| ValidationError::Invalid {
location: "#".to_string(),
reason: format!("invalid schema: {e}"),
})
}
pub fn validate_json(
schema: &serde_json::Value,
instance: &serde_json::Value,
) -> Result<(), ValidationError> {
let validator = compile_schema(schema)?;
if let Some(err) = validator.iter_errors(instance).next() {
let path = err.instance_path().to_string();
let location = if path.is_empty() {
"#".to_string()
} else {
format!("#{path}")
};
return Err(ValidationError::Invalid {
location,
reason: err.to_string(),
});
}
Ok(())
}