use alloc::string::String;
use alloc::vec::Vec;
use crate::compiler_context::CompilerContext;
use crate::draft::Draft;
use crate::error::{ValidationError, ValidationErrorKind};
use crate::keywords::additional_properties::{AdditionalPropertiesValidator, AdditionalSchema};
use crate::keywords::all_of::AllOfValidator;
use crate::keywords::any_of::AnyOfValidator;
use crate::keywords::const_::ConstValidator;
use crate::keywords::contains::ContainsValidator;
use crate::keywords::content::{
ContentCombinedValidator, ContentEncoding, ContentEncodingValidator, ContentMediaTypeValidator,
};
use crate::keywords::content_schema::ContentSchemaValidator;
use crate::keywords::dependent_required::DependentRequiredValidator;
use crate::keywords::dependent_schemas::DependentSchemasValidator;
use crate::keywords::enum_::EnumValidator;
use crate::keywords::exclusive_maximum::ExclusiveMaximumValidator;
use crate::keywords::exclusive_minimum::ExclusiveMinimumValidator;
use crate::keywords::format::FormatValidator;
use crate::keywords::if_::IfThenElseValidator;
use crate::keywords::items::ItemsValidator;
use crate::keywords::legacy::{DependenciesValidator, Dependency};
use crate::keywords::max_items::MaxItemsValidator;
use crate::keywords::max_length::MaxLengthValidator;
use crate::keywords::max_properties::MaxPropertiesValidator;
use crate::keywords::maximum::MaximumValidator;
use crate::keywords::min_items::MinItemsValidator;
use crate::keywords::min_length::MinLengthValidator;
use crate::keywords::min_properties::MinPropertiesValidator;
use crate::keywords::minimum::MinimumValidator;
use crate::keywords::multiple_of::MultipleOfValidator;
use crate::keywords::not_::NotValidator;
use crate::keywords::one_of::OneOfValidator;
use crate::keywords::pattern::PatternValidator;
use crate::keywords::pattern_properties::PatternPropertiesValidator;
use crate::keywords::prefix_items::PrefixItemsValidator;
use crate::keywords::properties::PropertiesValidator;
use crate::keywords::property_names::PropertyNamesValidator;
use crate::keywords::ref_::{DynamicRefValidator, RecursiveRefValidator, RefValidator};
use crate::keywords::required::RequiredValidator;
use crate::keywords::tuple_items::{AdditionalItemsPolicy, TupleItemsValidator};
use crate::keywords::type_::TypeValidator;
use crate::keywords::unevaluated_items::UnevaluatedItemsValidator;
use crate::keywords::unevaluated_properties::UnevaluatedPropertiesValidator;
use crate::keywords::unique_items::UniqueItemsValidator;
use crate::keywords::BoxedValidator;
use crate::node::SchemaNode;
use crate::paths::Location;
use crate::referencing::{Registry, VocabularySet};
use foundation_errstacks::IntoErrorTrace;
use serde_json::Value;
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
fn value_to_u64(value: &Value) -> Option<u64> {
value.as_u64().or_else(|| {
value.as_f64().and_then(|f| {
if f >= 0.0 && f.fract() == 0.0 && f <= u64::MAX as f64 {
Some(f as u64)
} else {
None
}
})
})
}
#[allow(dead_code)]
pub(crate) fn compile(
schema: &Value,
registry: &Registry,
draft: Draft,
assert_format: bool,
custom_formats: alloc::collections::BTreeMap<
alloc::string::String,
Box<dyn crate::formats::FormatChecker>,
>,
custom_keywords: alloc::collections::BTreeMap<
alloc::string::String,
Box<dyn crate::keywords::custom::KeywordFactory>,
>,
) -> Result<SchemaNode, ValidationError> {
let id_keyword = draft.id_keyword();
let base_uri = schema
.as_object()
.and_then(|o| o.get(id_keyword))
.and_then(|v| v.as_str())
.map_or("", |s| s.strip_suffix('#').unwrap_or(s));
let resolver = registry.resolver(base_uri);
let schema_path = Location::new();
let vocabulary = VocabularySet::for_draft(draft);
let ctx = CompilerContext::new(
resolver,
schema_path,
vocabulary,
draft,
assert_format,
custom_formats,
custom_keywords,
);
compile_node(schema, &ctx)
}
fn compile_node(schema: &Value, ctx: &CompilerContext) -> Result<SchemaNode, ValidationError> {
if ctx.depth > 128 {
return Err(ValidationErrorKind::Schema {
reason: "schema exceeds maximum nesting depth (128)".into(),
}
.into_error_trace());
}
if let Value::Bool(b) = schema {
return if *b {
Ok(SchemaNode::AlwaysValid)
} else {
Ok(SchemaNode::AlwaysInvalid {
schema_path: ctx.schema_path.clone(),
})
};
}
let schema_obj = schema.as_object().ok_or_else(|| {
ValidationErrorKind::Schema {
reason: "schema must be an object or boolean".into(),
}
.into_error_trace()
})?;
let defined_properties: alloc::collections::BTreeSet<alloc::string::String> = schema_obj
.get("properties")
.and_then(|p| p.as_object())
.map(|o| o.keys().cloned().collect())
.unwrap_or_default();
let pattern_regexes: Vec<(regex::Regex, alloc::string::String)> = schema_obj
.get("patternProperties")
.and_then(|p| p.as_object())
.map(|o| {
o.iter()
.filter_map(|(k, _)| regex::Regex::new(k).ok().map(|r| (r, k.clone())))
.collect()
})
.unwrap_or_default();
let mut validators: Vec<BoxedValidator> = Vec::new();
let has_ref = schema_obj.contains_key("$ref");
let ref_suppresses = !matches!(ctx.draft, Draft::Draft202012);
for (key, value) in schema_obj {
let is_uri_keyword = matches!(
key.as_str(),
"$id" | "$anchor" | "$dynamicAnchor" | "$recursiveAnchor"
);
if ref_suppresses && has_ref && key != "$ref" && !is_uri_keyword {
continue;
}
let keyword_ctx = ctx.push_keyword(key);
if let Some(result) = compile_keyword(
key,
value,
schema_obj,
&keyword_ctx,
&defined_properties,
&pattern_regexes,
) {
validators.push(result);
}
}
Ok(SchemaNode::Validators {
validators,
schema_path: ctx.schema_path.clone(),
})
}
fn compile_keyword(
keyword: &str,
value: &Value,
schema_obj: &serde_json::Map<String, Value>,
ctx: &CompilerContext<'_>,
defined_properties: &alloc::collections::BTreeSet<alloc::string::String>,
pattern_regexes: &[(regex::Regex, alloc::string::String)],
) -> Option<BoxedValidator> {
if let Some(factory) = ctx.custom_keywords.get(keyword) {
match factory.compile(value, ctx.schema_path.clone()) {
Ok(validator) => return Some(validator),
Err(_) => return None,
}
}
if !ctx.vocabulary.contains_keyword(keyword) {
return None;
}
match keyword {
"type" => compile_type(value, ctx),
"const" => Some(compile_const(value, ctx)),
"enum" => Some(compile_enum(value, ctx)),
"minLength" => Some(compile_min_length(value, ctx)),
"maxLength" => Some(compile_max_length(value, ctx)),
"pattern" => compile_pattern(value, ctx),
"format" => Some(compile_format(value, ctx)),
"minimum" => Some(compile_minimum(value, ctx, schema_obj)),
"maximum" => Some(compile_maximum(value, ctx, schema_obj)),
"exclusiveMinimum" => compile_exclusive_minimum(value, ctx),
"exclusiveMaximum" => compile_exclusive_maximum(value, ctx),
"multipleOf" => compile_multiple_of(value, ctx),
"required" => Some(compile_required(value, ctx)),
"minProperties" => Some(compile_min_properties(value, ctx)),
"maxProperties" => Some(compile_max_properties(value, ctx)),
"propertyNames" => Some(compile_property_names(value, ctx)),
"dependentRequired" => Some(compile_dependent_required(value, ctx)),
"properties" => compile_properties(value, ctx),
"additionalProperties" => {
compile_additional_properties(value, ctx, defined_properties, pattern_regexes)
}
"patternProperties" => compile_pattern_properties(value, ctx),
"dependentSchemas" => compile_dependent_schemas(value, ctx),
"unevaluatedProperties" => compile_unevaluated_properties(value, ctx),
"minItems" => Some(compile_min_items(value, ctx)),
"maxItems" => Some(compile_max_items(value, ctx)),
"uniqueItems" => compile_unique_items(value, ctx),
"items" => compile_items(value, ctx, schema_obj),
"prefixItems" => compile_prefix_items(value, ctx),
"contains" => compile_contains(value, ctx, schema_obj),
"unevaluatedItems" => compile_unevaluated_items(value, ctx),
"allOf" => compile_all_of(value, ctx),
"anyOf" => compile_any_of(value, ctx),
"oneOf" => compile_one_of(value, ctx),
"not" => compile_not(value, ctx),
"if" | "then" | "else" => compile_if_then_else(schema_obj, ctx),
"$ref" => compile_ref(value, ctx),
"$dynamicRef" => compile_dynamic_ref(value, ctx),
"$recursiveRef" => compile_recursive_ref(value, ctx),
"contentEncoding" if !matches!(ctx.draft, Draft::Draft201909 | Draft::Draft202012) => {
compile_content_encoding(value, ctx, schema_obj)
}
"contentMediaType" if !matches!(ctx.draft, Draft::Draft201909 | Draft::Draft202012) => {
Some(compile_content_media_type(value, ctx, schema_obj))
}
"contentSchema" if !matches!(ctx.draft, Draft::Draft201909 | Draft::Draft202012) => {
compile_content_schema(value, ctx, schema_obj)
}
"dependencies" => compile_dependencies(value, ctx),
_ => None,
}
}
fn compile_type(value: &Value, ctx: &CompilerContext<'_>) -> Option<BoxedValidator> {
use crate::types::JsonTypeSet;
let types = match value {
Value::String(s) => {
let mut set = JsonTypeSet::new();
if let Some(t) = parse_type_name(s) {
set.insert(t);
}
set
}
Value::Array(arr) => {
let mut set = JsonTypeSet::new();
for v in arr {
if let Some(s) = v.as_str() {
if let Some(t) = parse_type_name(s) {
set.insert(t);
}
}
}
set
}
_ => return None,
};
Some(Box::new(TypeValidator::new(types, ctx.schema_path.clone())))
}
fn parse_type_name(name: &str) -> Option<crate::types::JsonType> {
use crate::types::JsonType;
match name {
"null" => Some(JsonType::Null),
"boolean" => Some(JsonType::Boolean),
"object" => Some(JsonType::Object),
"array" => Some(JsonType::Array),
"string" => Some(JsonType::String),
"number" => Some(JsonType::Number),
"integer" => Some(JsonType::Integer),
_ => None,
}
}
fn compile_const(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
Box::new(ConstValidator::new(value.clone(), ctx.schema_path.clone()))
}
fn compile_enum(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
let options = value.as_array().cloned().unwrap_or_default();
Box::new(EnumValidator::new(options, ctx.schema_path.clone()))
}
fn compile_min_length(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
let min = value_to_u64(value).unwrap_or(0);
Box::new(MinLengthValidator::new(min, ctx.schema_path.clone()))
}
fn compile_max_length(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
let max = value_to_u64(value).unwrap_or(0);
Box::new(MaxLengthValidator::new(max, ctx.schema_path.clone()))
}
fn compile_pattern(value: &Value, ctx: &CompilerContext<'_>) -> Option<BoxedValidator> {
let pattern = value.as_str()?.to_string();
PatternValidator::new(pattern, ctx.schema_path.clone())
.map(|v| Box::new(v) as BoxedValidator)
.ok()
}
fn compile_format(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
let format_name = value.as_str().unwrap_or("").to_string();
let checker = ctx
.custom_formats
.get(&format_name)
.map(|c| c.clone_box())
.or_else(|| {
crate::formats::builtin_format(&format_name)
.map(super::formats::FormatChecker::clone_box)
});
Box::new(FormatValidator::new(
format_name,
ctx.schema_path.clone(),
checker,
ctx.assert_format,
))
}
fn compile_minimum(
value: &Value,
ctx: &CompilerContext<'_>,
schema_obj: &serde_json::Map<String, Value>,
) -> BoxedValidator {
let limit = value.as_f64().unwrap_or(0.0);
let exclusive = if ctx.draft == Draft::Draft4 {
schema_obj
.get("exclusiveMinimum")
.and_then(Value::as_bool)
.unwrap_or(false)
} else {
false
};
Box::new(MinimumValidator::new(
limit,
exclusive,
ctx.schema_path.clone(),
))
}
fn compile_maximum(
value: &Value,
ctx: &CompilerContext<'_>,
schema_obj: &serde_json::Map<String, Value>,
) -> BoxedValidator {
let limit = value.as_f64().unwrap_or(0.0);
let exclusive = if ctx.draft == Draft::Draft4 {
schema_obj
.get("exclusiveMaximum")
.and_then(Value::as_bool)
.unwrap_or(false)
} else {
false
};
Box::new(MaximumValidator::new(
limit,
exclusive,
ctx.schema_path.clone(),
))
}
fn compile_exclusive_minimum(value: &Value, ctx: &CompilerContext<'_>) -> Option<BoxedValidator> {
if ctx.draft == Draft::Draft4 {
return None;
}
let limit = value.as_f64().unwrap_or(0.0);
Some(Box::new(ExclusiveMinimumValidator::new(
limit,
ctx.schema_path.clone(),
)))
}
fn compile_exclusive_maximum(value: &Value, ctx: &CompilerContext<'_>) -> Option<BoxedValidator> {
if ctx.draft == Draft::Draft4 {
return None;
}
let limit = value.as_f64().unwrap_or(0.0);
Some(Box::new(ExclusiveMaximumValidator::new(
limit,
ctx.schema_path.clone(),
)))
}
fn compile_multiple_of(value: &Value, ctx: &CompilerContext<'_>) -> Option<BoxedValidator> {
let multiple = value.as_f64().unwrap_or(0.0);
if multiple == 0.0 {
return None;
}
Some(Box::new(MultipleOfValidator::new(
multiple,
ctx.schema_path.clone(),
)))
}
fn compile_required(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
let props = value
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
Box::new(RequiredValidator::new(props, ctx.schema_path.clone()))
}
fn compile_min_properties(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
let min = value_to_u64(value).unwrap_or(0);
Box::new(MinPropertiesValidator::new(min, ctx.schema_path.clone()))
}
fn compile_max_properties(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
let max = value_to_u64(value).unwrap_or(0);
Box::new(MaxPropertiesValidator::new(max, ctx.schema_path.clone()))
}
fn compile_property_names(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
let sub_ctx = ctx.push_keyword("propertyNames");
let schema = compile_node(value, &sub_ctx).unwrap_or(SchemaNode::AlwaysValid);
Box::new(PropertyNamesValidator::new(schema))
}
fn compile_dependent_required(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
use alloc::collections::BTreeMap;
let map = value
.as_object()
.map(|obj| {
obj.iter()
.filter_map(|(k, v)| {
let props: Vec<String> = v
.as_array()?
.iter()
.filter_map(|p| p.as_str().map(String::from))
.collect();
if props.is_empty() {
None
} else {
Some((k.clone(), props))
}
})
.collect::<BTreeMap<_, _>>()
})
.unwrap_or_default();
Box::new(DependentRequiredValidator::new(
map,
ctx.schema_path.clone(),
))
}
fn compile_all_of(value: &Value, ctx: &CompilerContext) -> Option<BoxedValidator> {
let arr = value.as_array()?;
let mut schemas = Vec::new();
for (i, sub) in arr.iter().enumerate() {
let sub_ctx = ctx.push_keyword(&format!("allOf/{i}"));
match compile_node(sub, &sub_ctx) {
Ok(node) => schemas.push(node),
Err(_) => return None,
}
}
Some(Box::new(AllOfValidator::new(schemas)))
}
fn compile_any_of(value: &Value, ctx: &CompilerContext) -> Option<BoxedValidator> {
let arr = value.as_array()?;
let mut schemas = Vec::new();
for (i, sub) in arr.iter().enumerate() {
let sub_ctx = ctx.push_keyword(&format!("anyOf/{i}"));
match compile_node(sub, &sub_ctx) {
Ok(node) => schemas.push(node),
Err(_) => return None,
}
}
Some(Box::new(AnyOfValidator::new(schemas)))
}
fn compile_one_of(value: &Value, ctx: &CompilerContext) -> Option<BoxedValidator> {
let arr = value.as_array()?;
let mut schemas = Vec::new();
for (i, sub) in arr.iter().enumerate() {
let sub_ctx = ctx.push_keyword(&format!("oneOf/{i}"));
match compile_node(sub, &sub_ctx) {
Ok(node) => schemas.push(node),
Err(_) => return None,
}
}
Some(Box::new(OneOfValidator::new(schemas)))
}
fn compile_not(value: &Value, ctx: &CompilerContext) -> Option<BoxedValidator> {
let sub_ctx = ctx.push_keyword("not");
match compile_node(value, &sub_ctx) {
Ok(node) => Some(Box::new(NotValidator::new(node))),
Err(_) => None,
}
}
fn compile_if_then_else(
schema_obj: &serde_json::Map<String, Value>,
ctx: &CompilerContext,
) -> Option<BoxedValidator> {
let if_val = schema_obj.get("if")?;
let if_ctx = ctx.push_keyword("if");
let if_schema = compile_node(if_val, &if_ctx).ok()?;
let then_schema = schema_obj
.get("then")
.map(|v| {
let then_ctx = ctx.push_keyword("then");
compile_node(v, &then_ctx)
})
.transpose()
.ok()?;
let else_schema = schema_obj
.get("else")
.map(|v| {
let else_ctx = ctx.push_keyword("else");
compile_node(v, &else_ctx)
})
.transpose()
.ok()?;
Some(Box::new(IfThenElseValidator::new(
if_schema,
then_schema,
else_schema,
)))
}
fn compile_properties(value: &Value, ctx: &CompilerContext) -> Option<BoxedValidator> {
let obj = value.as_object()?;
let mut props = alloc::collections::BTreeMap::new();
for (name, sub) in obj {
let sub_ctx = ctx.push_keyword(&format!("properties/{name}"));
match compile_node(sub, &sub_ctx) {
Ok(node) => {
props.insert(name.clone(), node);
}
Err(_) => return None,
}
}
Some(Box::new(PropertiesValidator::new(props)))
}
fn compile_additional_properties(
value: &Value,
ctx: &CompilerContext,
defined_properties: &alloc::collections::BTreeSet<alloc::string::String>,
pattern_regexes: &[(regex::Regex, alloc::string::String)],
) -> Option<BoxedValidator> {
let schema = match value {
Value::Bool(false) => Some(AdditionalSchema::False),
Value::Bool(true) => Some(AdditionalSchema::Schema(SchemaNode::AlwaysValid)),
Value::Object(_) | Value::Array(_) => {
let sub_ctx = ctx.push_keyword("additionalProperties");
match compile_node(value, &sub_ctx) {
Ok(node) => Some(AdditionalSchema::Schema(node)),
Err(_) => return None,
}
}
_ => None,
};
Some(Box::new(AdditionalPropertiesValidator::new(
schema,
defined_properties.clone(),
pattern_regexes
.iter()
.map(|(r, s)| (r.clone(), s.clone()))
.collect(),
)))
}
fn compile_pattern_properties(value: &Value, ctx: &CompilerContext) -> Option<BoxedValidator> {
let obj = value.as_object()?;
let mut patterns = Vec::new();
for (pattern, sub) in obj {
let regex = regex::Regex::new(pattern).ok()?;
let sub_ctx = ctx.push_keyword(&format!("patternProperties/{pattern}"));
match compile_node(sub, &sub_ctx) {
Ok(node) => patterns.push((regex, node)),
Err(_) => return None,
}
}
Some(Box::new(PatternPropertiesValidator::new(patterns)))
}
fn compile_dependent_schemas(value: &Value, ctx: &CompilerContext) -> Option<BoxedValidator> {
let obj = value.as_object()?;
let mut deps = alloc::collections::BTreeMap::new();
for (name, sub) in obj {
if sub.is_object() || sub.is_boolean() {
let sub_ctx = ctx.push_keyword(&format!("dependentSchemas/{name}"));
match compile_node(sub, &sub_ctx) {
Ok(node) => {
deps.insert(name.clone(), node);
}
Err(_) => return None,
}
}
}
if deps.is_empty() {
return None;
}
Some(Box::new(DependentSchemasValidator::new(deps)))
}
fn compile_unevaluated_properties(value: &Value, ctx: &CompilerContext) -> Option<BoxedValidator> {
let sub_ctx = ctx.push_keyword("unevaluatedProperties");
match compile_node(value, &sub_ctx) {
Ok(node) => Some(Box::new(UnevaluatedPropertiesValidator::new(node))),
Err(_) => None,
}
}
fn compile_items(
value: &Value,
ctx: &CompilerContext,
schema_obj: &serde_json::Map<String, Value>,
) -> Option<BoxedValidator> {
match value {
Value::Bool(b) => {
match ctx.draft {
Draft::Draft202012 => {
let skip_first = schema_obj
.get("prefixItems")
.and_then(Value::as_array)
.map_or(0, std::vec::Vec::len);
let sub_ctx = ctx.push_keyword("items");
match compile_node(value, &sub_ctx) {
Ok(node) => {
if *b || skip_first > 0 {
Some(Box::new(ItemsValidator::with_offset(node, skip_first)))
} else {
Some(Box::new(ItemsValidator::new(node)))
}
}
Err(_) => None,
}
}
_ => {
if *b {
None } else {
Some(Box::new(TupleItemsValidator::new(
vec![],
AdditionalItemsPolicy::RejectAll,
)))
}
}
}
}
Value::Object(_) => {
let sub_ctx = ctx.push_keyword("items");
match compile_node(value, &sub_ctx) {
Ok(node) => {
let skip_first = if matches!(ctx.draft, Draft::Draft202012) {
schema_obj
.get("prefixItems")
.and_then(Value::as_array)
.map_or(0, std::vec::Vec::len)
} else {
0
};
if skip_first > 0 {
Some(Box::new(ItemsValidator::with_offset(node, skip_first)))
} else {
Some(Box::new(ItemsValidator::new(node)))
}
}
Err(_) => None,
}
}
Value::Array(arr) => {
if ctx.draft == Draft::Draft202012 {
None
} else {
let mut schemas = Vec::new();
for (i, sub) in arr.iter().enumerate() {
let sub_ctx = ctx.push_keyword(&format!("items/{i}"));
match compile_node(sub, &sub_ctx) {
Ok(node) => schemas.push(node),
Err(_) => return None,
}
}
let additional = schema_obj
.get("additionalItems")
.map_or(AdditionalItemsPolicy::AllowAny, |v| {
compile_additional_schema(v, ctx)
});
Some(Box::new(TupleItemsValidator::new(schemas, additional)))
}
}
_ => None,
}
}
fn compile_additional_schema(value: &Value, ctx: &CompilerContext) -> AdditionalItemsPolicy {
match value {
Value::Bool(false) => AdditionalItemsPolicy::RejectAll,
Value::Object(_) => {
let sub_ctx = ctx.push_keyword("additionalItems");
match compile_node(value, &sub_ctx) {
Ok(node) => AdditionalItemsPolicy::Validate(node),
Err(_) => AdditionalItemsPolicy::AllowAny,
}
}
_ => AdditionalItemsPolicy::AllowAny,
}
}
fn compile_prefix_items(value: &Value, ctx: &CompilerContext) -> Option<BoxedValidator> {
let arr = value.as_array()?;
let mut items = Vec::new();
for (i, sub) in arr.iter().enumerate() {
let sub_ctx = ctx.push_keyword(&format!("prefixItems/{i}"));
match compile_node(sub, &sub_ctx) {
Ok(node) => items.push(node),
Err(_) => return None,
}
}
Some(Box::new(PrefixItemsValidator::new(items)))
}
fn compile_contains(
value: &Value,
ctx: &CompilerContext,
schema_obj: &serde_json::Map<String, Value>,
) -> Option<BoxedValidator> {
let sub_ctx = ctx.push_keyword("contains");
let Ok(node) = compile_node(value, &sub_ctx) else {
return None;
};
let mut validator = ContainsValidator::new(node);
if ctx.draft >= Draft::Draft201909 {
if let Some(min) = schema_obj.get("minContains").and_then(value_to_u64) {
validator = validator.with_min(min);
}
if let Some(max) = schema_obj.get("maxContains").and_then(value_to_u64) {
validator = validator.with_max(max);
}
}
Some(Box::new(validator))
}
fn compile_unevaluated_items(value: &Value, ctx: &CompilerContext) -> Option<BoxedValidator> {
let sub_ctx = ctx.push_keyword("unevaluatedItems");
match compile_node(value, &sub_ctx) {
Ok(node) => Some(Box::new(UnevaluatedItemsValidator::new(node))),
Err(_) => None,
}
}
fn resolve_ref_uri(base_uri: &str, reference: &str) -> String {
if let Some(frag) = reference.strip_prefix('#') {
alloc::format!("{base_uri}#{frag}")
} else {
reference.to_string()
}
}
fn compile_ref(value: &Value, ctx: &CompilerContext<'_>) -> Option<BoxedValidator> {
let reference = value.as_str()?.to_string();
let resolved = ctx.resolver.lookup(&reference).ok()?;
let cycle_key = resolve_ref_uri(resolved.resolver().base_uri(), &reference);
if ctx.is_in_progress(&cycle_key) {
return Some(Box::new(RefValidator::new(
reference,
ctx.schema_path.clone(),
SchemaNode::AlwaysValid,
)));
}
ctx.mark_in_progress(&cycle_key);
let sub_ctx = ctx.with_resolver(resolved.resolver().clone(), "$ref");
let resolved_schema = compile_node(resolved.contents(), &sub_ctx).ok()?;
ctx.mark_done(&cycle_key);
Some(Box::new(RefValidator::new(
reference,
ctx.schema_path.clone(),
resolved_schema,
)))
}
fn compile_dynamic_ref(value: &Value, ctx: &CompilerContext<'_>) -> Option<BoxedValidator> {
let reference = value.as_str()?.to_string();
let resolved = ctx.resolver.lookup(&reference).ok()?;
let cycle_key = resolve_ref_uri(resolved.resolver().base_uri(), &reference);
if ctx.is_in_progress(&cycle_key) {
return Some(Box::new(DynamicRefValidator::new(
reference,
ctx.schema_path.clone(),
SchemaNode::AlwaysValid,
)));
}
ctx.mark_in_progress(&cycle_key);
let sub_ctx = ctx.with_resolver(resolved.resolver().clone(), "$dynamicRef");
let resolved_schema = compile_node(resolved.contents(), &sub_ctx).ok()?;
ctx.mark_done(&cycle_key);
Some(Box::new(DynamicRefValidator::new(
reference,
ctx.schema_path.clone(),
resolved_schema,
)))
}
fn compile_recursive_ref(value: &Value, ctx: &CompilerContext<'_>) -> Option<BoxedValidator> {
let reference = value.as_str()?.to_string();
let resolved = if reference == "#" || reference.is_empty() {
ctx.resolver.lookup_recursive_ref().ok()?
} else {
ctx.resolver.lookup(&reference).ok()?
};
let cycle_key = resolve_ref_uri(resolved.resolver().base_uri(), &reference);
if ctx.is_in_progress(&cycle_key) {
return Some(Box::new(RecursiveRefValidator::new(
reference,
ctx.schema_path.clone(),
SchemaNode::AlwaysValid,
)));
}
ctx.mark_in_progress(&cycle_key);
let sub_ctx = ctx.with_resolver(resolved.resolver().clone(), "$recursiveRef");
let resolved_schema = compile_node(resolved.contents(), &sub_ctx).ok()?;
ctx.mark_done(&cycle_key);
Some(Box::new(RecursiveRefValidator::new(
reference,
ctx.schema_path.clone(),
resolved_schema,
)))
}
fn compile_content_encoding(
value: &Value,
ctx: &CompilerContext<'_>,
schema_obj: &serde_json::Map<String, Value>,
) -> Option<BoxedValidator> {
let encoding = value.as_str()?.to_string();
if schema_obj.contains_key("contentMediaType") {
return None; }
Some(Box::new(ContentEncodingValidator::new(
&encoding,
ctx.schema_path.clone(),
)))
}
fn compile_content_media_type(
value: &Value,
ctx: &CompilerContext<'_>,
schema_obj: &serde_json::Map<String, Value>,
) -> BoxedValidator {
let media_type = value.as_str().unwrap_or("").to_string();
if let Some(Value::String(encoding)) = schema_obj.get("contentEncoding") {
let mut enc_path = ctx.schema_path.clone();
enc_path.push_property("contentEncoding");
let mut mt_path = ctx.schema_path.clone();
mt_path.push_property("contentMediaType");
return Box::new(ContentCombinedValidator::new(
ContentEncoding::from_name(encoding),
media_type,
enc_path,
mt_path,
));
}
Box::new(ContentMediaTypeValidator::new(
media_type,
ctx.schema_path.clone(),
))
}
fn compile_content_schema(
value: &Value,
ctx: &CompilerContext<'_>,
schema_obj: &serde_json::Map<String, Value>,
) -> Option<BoxedValidator> {
let sub_ctx = ctx.push_keyword("contentSchema");
let schema = compile_node(value, &sub_ctx).ok()?;
let encoding = schema_obj
.get("contentEncoding")
.and_then(Value::as_str)
.map(ContentEncoding::from_name);
let media_type = schema_obj
.get("contentMediaType")
.and_then(Value::as_str)
.map(String::from);
Some(Box::new(ContentSchemaValidator::new(
schema,
encoding,
media_type,
ctx.schema_path.clone(),
)))
}
fn compile_min_items(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
let min = value_to_u64(value).unwrap_or(0);
Box::new(MinItemsValidator::new(min, ctx.schema_path.clone()))
}
fn compile_max_items(value: &Value, ctx: &CompilerContext<'_>) -> BoxedValidator {
let max = value_to_u64(value).unwrap_or(0);
Box::new(MaxItemsValidator::new(max, ctx.schema_path.clone()))
}
fn compile_unique_items(value: &Value, ctx: &CompilerContext<'_>) -> Option<BoxedValidator> {
if value.as_bool().unwrap_or(false) {
Some(Box::new(UniqueItemsValidator::new(ctx.schema_path.clone())))
} else {
None
}
}
fn compile_dependencies(value: &Value, ctx: &CompilerContext<'_>) -> Option<BoxedValidator> {
let obj = value.as_object()?;
let mut deps = alloc::collections::BTreeMap::new();
for (name, dep_value) in obj {
if let Some(arr) = dep_value.as_array() {
let props: Vec<String> = arr
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect();
if !props.is_empty() {
deps.insert(name.clone(), Dependency::Required(props));
}
} else if dep_value.is_object() || dep_value.is_boolean() {
let sub_ctx = ctx.push_keyword(&format!("dependencies/{name}"));
if let Ok(schema) = compile_node(dep_value, &sub_ctx) {
deps.insert(name.clone(), Dependency::Schema(schema));
}
}
}
if deps.is_empty() {
return None;
}
Some(Box::new(DependenciesValidator::new(deps)))
}