Files
jspg/src/validator/rules/object.rs

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)
}
}