221 lines
7.5 KiB
Rust
221 lines
7.5 KiB
Rust
use std::collections::HashSet;
|
|
|
|
use serde_json::Value;
|
|
|
|
use crate::validator::context::ValidationContext;
|
|
use crate::validator::error::ValidationError;
|
|
use crate::validator::result::ValidationResult;
|
|
|
|
impl<'a> ValidationContext<'a> {
|
|
pub(crate) fn validate_object(
|
|
&self,
|
|
result: &mut ValidationResult,
|
|
) -> Result<bool, ValidationError> {
|
|
let current = self.instance;
|
|
if let Some(obj) = current.as_object() {
|
|
// Entity Bound Implicit Type Validation
|
|
if let Some(lookup_key) = self.schema.id.as_ref().or(self.schema.r#ref.as_ref()) {
|
|
let base_type_name = lookup_key.split('.').next_back().unwrap_or("").to_string();
|
|
if let Some(type_def) = self.db.types.get(&base_type_name)
|
|
&& let Some(type_val) = obj.get("type")
|
|
&& let Some(type_str) = type_val.as_str()
|
|
{
|
|
if type_def.variations.contains(type_str) {
|
|
// Ensure it passes strict mode
|
|
result.evaluated_keys.insert("type".to_string());
|
|
} else {
|
|
result.errors.push(ValidationError {
|
|
code: "CONST_VIOLATED".to_string(), // Aligning with original const override errors
|
|
message: format!(
|
|
"Type '{}' is not a valid descendant for this entity bound schema",
|
|
type_str
|
|
),
|
|
path: format!("{}/type", self.path),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if let Some(min) = self.schema.min_properties
|
|
&& (obj.len() as f64) < min
|
|
{
|
|
result.errors.push(ValidationError {
|
|
code: "MIN_PROPERTIES".to_string(),
|
|
message: "Too few properties".to_string(),
|
|
path: self.path.to_string(),
|
|
});
|
|
}
|
|
if let Some(max) = self.schema.max_properties
|
|
&& (obj.len() as f64) > max
|
|
{
|
|
result.errors.push(ValidationError {
|
|
code: "MAX_PROPERTIES".to_string(),
|
|
message: "Too many properties".to_string(),
|
|
path: self.path.to_string(),
|
|
});
|
|
}
|
|
if let Some(ref req) = self.schema.required {
|
|
for field in req {
|
|
if !obj.contains_key(field) {
|
|
result.errors.push(ValidationError {
|
|
code: "REQUIRED_FIELD_MISSING".to_string(),
|
|
message: format!("Missing {}", field),
|
|
path: format!("{}/{}", self.path, field),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(ref deps) = self.schema.dependencies {
|
|
for (prop, dep) in deps {
|
|
if obj.contains_key(prop) {
|
|
match dep {
|
|
crate::database::schema::Dependency::Props(required_props) => {
|
|
for req_prop in required_props {
|
|
if !obj.contains_key(req_prop) {
|
|
result.errors.push(ValidationError {
|
|
code: "DEPENDENCY_MISSING".to_string(),
|
|
message: format!("Property '{}' requires property '{}'", prop, req_prop),
|
|
path: self.path.to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
crate::database::schema::Dependency::Schema(dep_schema) => {
|
|
let derived = self.derive_for_schema(dep_schema, false);
|
|
let dep_res = derived.validate()?;
|
|
result.evaluated_keys.extend(dep_res.evaluated_keys.clone());
|
|
result.merge(dep_res);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(props) = &self.schema.properties {
|
|
for (key, sub_schema) in props {
|
|
if self.overrides.contains(key) {
|
|
continue; // Skip validation if exactly this property was overridden by a child
|
|
}
|
|
|
|
if let Some(child_instance) = obj.get(key) {
|
|
let new_path = format!("{}/{}", self.path, key);
|
|
let is_ref = sub_schema.r#ref.is_some();
|
|
let next_extensible = if is_ref { false } else { self.extensible };
|
|
|
|
let derived = self.derive(
|
|
sub_schema,
|
|
child_instance,
|
|
&new_path,
|
|
HashSet::new(),
|
|
next_extensible,
|
|
false,
|
|
);
|
|
let mut item_res = derived.validate()?;
|
|
|
|
// Entity Bound Implicit Type Interception
|
|
if key == "type"
|
|
&& let Some(lookup_key) = sub_schema.id.as_ref().or(sub_schema.r#ref.as_ref())
|
|
{
|
|
let base_type_name = lookup_key.split('.').next_back().unwrap_or("").to_string();
|
|
if let Some(type_def) = self.db.types.get(&base_type_name)
|
|
&& let Some(instance_type) = child_instance.as_str()
|
|
&& type_def.variations.contains(instance_type)
|
|
{
|
|
item_res
|
|
.errors
|
|
.retain(|e| e.code != "CONST_VIOLATED" && e.code != "ENUM_VIOLATED");
|
|
}
|
|
}
|
|
|
|
result.merge(item_res);
|
|
result.evaluated_keys.insert(key.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(ref compiled_pp) = self.schema.compiled_pattern_properties {
|
|
for (compiled_re, sub_schema) in compiled_pp {
|
|
for (key, child_instance) in obj {
|
|
if compiled_re.0.is_match(key) {
|
|
let new_path = format!("{}/{}", self.path, key);
|
|
let is_ref = sub_schema.r#ref.is_some();
|
|
let next_extensible = if is_ref { false } else { self.extensible };
|
|
|
|
let derived = self.derive(
|
|
sub_schema,
|
|
child_instance,
|
|
&new_path,
|
|
HashSet::new(),
|
|
next_extensible,
|
|
false,
|
|
);
|
|
let item_res = derived.validate()?;
|
|
result.merge(item_res);
|
|
result.evaluated_keys.insert(key.to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(ref additional_schema) = self.schema.additional_properties {
|
|
for (key, child_instance) in obj {
|
|
let mut locally_matched = false;
|
|
if let Some(props) = &self.schema.properties
|
|
&& props.contains_key(&key.to_string())
|
|
{
|
|
locally_matched = true;
|
|
}
|
|
if !locally_matched && let Some(ref compiled_pp) = self.schema.compiled_pattern_properties
|
|
{
|
|
for (compiled_re, _) in compiled_pp {
|
|
if compiled_re.0.is_match(key) {
|
|
locally_matched = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if !locally_matched {
|
|
let new_path = format!("{}/{}", self.path, key);
|
|
let is_ref = additional_schema.r#ref.is_some();
|
|
let next_extensible = if is_ref { false } else { self.extensible };
|
|
|
|
let derived = self.derive(
|
|
additional_schema,
|
|
child_instance,
|
|
&new_path,
|
|
HashSet::new(),
|
|
next_extensible,
|
|
false,
|
|
);
|
|
let item_res = derived.validate()?;
|
|
result.merge(item_res);
|
|
result.evaluated_keys.insert(key.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(ref property_names) = self.schema.property_names {
|
|
for key in obj.keys() {
|
|
let _new_path = format!("{}/propertyNames/{}", self.path, key);
|
|
let val_str = Value::String(key.to_string());
|
|
|
|
let ctx = ValidationContext::new(
|
|
self.db,
|
|
self.root,
|
|
property_names,
|
|
&val_str,
|
|
HashSet::new(),
|
|
self.extensible,
|
|
self.reporter,
|
|
);
|
|
|
|
result.merge(ctx.validate()?);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
}
|