272 lines
9.8 KiB
Rust
272 lines
9.8 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 implicit type validation
|
|
if let Some(schema_identifier) = self.schema.identifier() {
|
|
// We decompose identity string routing inherently
|
|
let expected_type = schema_identifier.split('.').last().unwrap_or(&schema_identifier);
|
|
|
|
// Check if the identifier represents a registered global database entity boundary mathematically
|
|
if let Some(type_def) = self.db.types.get(expected_type) {
|
|
if let Some(type_val) = obj.get("type") {
|
|
if let Some(type_str) = type_val.as_str() {
|
|
if type_def.variations.contains(type_str) {
|
|
// The instance is validly declaring a known structural descent
|
|
result.evaluated_keys.insert("type".to_string());
|
|
} else {
|
|
result.errors.push(ValidationError {
|
|
code: "CONST_VIOLATED".to_string(), // Aligning with original const override errors natively
|
|
message: format!(
|
|
"Type '{}' is not a valid descendant for this entity bound schema",
|
|
type_str
|
|
),
|
|
path: self.join_path("type"),
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
// Because it's a global entity target, the payload must structurally provide a discriminator natively
|
|
result.errors.push(ValidationError {
|
|
code: "MISSING_TYPE".to_string(),
|
|
message: format!("Schema mechanically requires type discrimination '{}'", expected_type),
|
|
path: self.path.clone(), // Empty boundary
|
|
});
|
|
}
|
|
|
|
// If the target mathematically declares a horizontal structural STI variation natively
|
|
if schema_identifier.contains('.') {
|
|
if obj.get("kind").is_none() {
|
|
result.errors.push(ValidationError {
|
|
code: "MISSING_KIND".to_string(),
|
|
message: "Schema mechanically requires horizontal kind discrimination".to_string(),
|
|
path: self.path.clone(),
|
|
});
|
|
} else {
|
|
result.evaluated_keys.insert("kind".to_string());
|
|
}
|
|
}
|
|
} else {
|
|
// If it isn't registered globally, it might be a nested Ad-Hoc candidate running via O(1) union routers.
|
|
// Because they lack manual type property descriptors, we natively shield "type" and "kind" keys from
|
|
// triggering additionalProperty violations natively IF they precisely correspond to their fast-path boundaries
|
|
if let Some(type_val) = obj.get("type") {
|
|
if let Some(type_str) = type_val.as_str() {
|
|
if type_str == expected_type {
|
|
result.evaluated_keys.insert("type".to_string());
|
|
}
|
|
}
|
|
}
|
|
if let Some(kind_val) = obj.get("kind") {
|
|
if let Some((kind_str, _)) = schema_identifier.rsplit_once('.') {
|
|
if let Some(actual_kind) = kind_val.as_str() {
|
|
if actual_kind == kind_str {
|
|
result.evaluated_keys.insert("kind".to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if field == "type" {
|
|
result.errors.push(ValidationError {
|
|
code: "MISSING_TYPE".to_string(),
|
|
message: "Missing type discriminator".to_string(),
|
|
path: self.join_path(field),
|
|
});
|
|
} else {
|
|
result.errors.push(ValidationError {
|
|
code: "REQUIRED_FIELD_MISSING".to_string(),
|
|
message: format!("Missing {}", field),
|
|
path: self.join_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 = self.join_path(key);
|
|
let is_ref = match &sub_schema.type_ {
|
|
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) => !crate::database::schema::is_primitive_type(t),
|
|
_ => false,
|
|
};
|
|
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(compiled_pp) = self.schema.compiled_pattern_properties.get() {
|
|
for (compiled_re, sub_schema) in compiled_pp {
|
|
for (key, child_instance) in obj {
|
|
if compiled_re.0.is_match(key) {
|
|
let new_path = self.join_path(key);
|
|
let is_ref = match &sub_schema.type_ {
|
|
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) => !crate::database::schema::is_primitive_type(t),
|
|
_ => false,
|
|
};
|
|
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(compiled_pp) = self.schema.compiled_pattern_properties.get()
|
|
{
|
|
for (compiled_re, _) in compiled_pp {
|
|
if compiled_re.0.is_match(key) {
|
|
locally_matched = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if !locally_matched {
|
|
let new_path = self.join_path(key);
|
|
let is_ref = match &additional_schema.type_ {
|
|
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) => !crate::database::schema::is_primitive_type(t),
|
|
_ => false,
|
|
};
|
|
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 = self.join_path(&format!("propertyNames/{}", 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)
|
|
}
|
|
}
|