255 lines
8.4 KiB
Rust
255 lines
8.4 KiB
Rust
use crate::validator::context::ValidationContext;
|
|
use crate::validator::error::ValidationError;
|
|
use crate::validator::result::ValidationResult;
|
|
use crate::database::realm::SchemaRealm;
|
|
|
|
impl<'a> ValidationContext<'a> {
|
|
pub(crate) fn validate_family(
|
|
&self,
|
|
result: &mut ValidationResult,
|
|
) -> Result<bool, ValidationError> {
|
|
if self.schema.family.is_some() {
|
|
let conflicts = self.schema.type_.is_some()
|
|
|| self.schema.properties.is_some()
|
|
|| self.schema.required.is_some()
|
|
|| self.schema.additional_properties.is_some()
|
|
|| self.schema.items.is_some()
|
|
|| self.schema.cases.is_some()
|
|
|| self.schema.one_of.is_some()
|
|
|| self.schema.enum_.is_some()
|
|
|| self.schema.const_.is_some();
|
|
|
|
if conflicts {
|
|
result.errors.push(ValidationError {
|
|
code: "INVALID_SCHEMA".to_string(),
|
|
message: "family must be used exclusively without other constraints".to_string(),
|
|
path: self.path.to_string(),
|
|
});
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
if self.schema.family.is_some() {
|
|
if let Some(options) = self.schema.compiled_options.get() {
|
|
return self.execute_polymorph(options, result);
|
|
} else {
|
|
result.errors.push(ValidationError {
|
|
code: "UNCOMPILED_FAMILY".to_string(),
|
|
message: "Encountered family block that could not be mapped to deterministic options during db schema compilation.".to_string(),
|
|
path: self.path.to_string(),
|
|
});
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
pub(crate) fn validate_one_of(
|
|
&self,
|
|
result: &mut ValidationResult,
|
|
) -> Result<bool, ValidationError> {
|
|
if self.schema.one_of.is_some() {
|
|
if let Some(options) = self.schema.compiled_options.get() {
|
|
return self.execute_polymorph(options, result);
|
|
} else {
|
|
result.errors.push(ValidationError {
|
|
code: "UNCOMPILED_ONEOF".to_string(),
|
|
message: "Encountered oneOf block that could not be mapped to deterministic compiled options natively.".to_string(),
|
|
path: self.path.to_string(),
|
|
});
|
|
return Ok(false);
|
|
}
|
|
}
|
|
Ok(true)
|
|
}
|
|
|
|
pub(crate) fn execute_polymorph(
|
|
&self,
|
|
options: &std::collections::BTreeMap<String, (Option<usize>, Option<String>)>,
|
|
result: &mut ValidationResult,
|
|
) -> Result<bool, ValidationError> {
|
|
// 1. O(1) Fast-Path Router & Extractor
|
|
let instance_val = if let Some(disc) = self.schema.compiled_discriminator.get() {
|
|
let val = self
|
|
.instance
|
|
.as_object()
|
|
.and_then(|o| o.get(disc))
|
|
.and_then(|t| t.as_str());
|
|
if val.is_some() {
|
|
result.evaluated_keys.insert(disc.to_string());
|
|
}
|
|
val.map(|s| s.to_string())
|
|
} else {
|
|
match self.instance {
|
|
serde_json::Value::Null => Some("null".to_string()),
|
|
serde_json::Value::Bool(_) => Some("boolean".to_string()),
|
|
serde_json::Value::Number(n) => {
|
|
if n.is_i64() || n.is_u64() {
|
|
Some("integer".to_string())
|
|
} else {
|
|
Some("number".to_string())
|
|
}
|
|
}
|
|
serde_json::Value::String(_) => Some("string".to_string()),
|
|
serde_json::Value::Array(_) => Some("array".to_string()),
|
|
serde_json::Value::Object(_) => Some("object".to_string()),
|
|
}
|
|
};
|
|
|
|
if let Some(val) = instance_val {
|
|
if let Some((idx_opt, target_id_opt)) = options.get(&val) {
|
|
if let Some(target_id) = target_id_opt {
|
|
if let Some(target_schema) = self.db.get_scoped_schema(SchemaRealm::Type, target_id) {
|
|
let derived = self.derive_for_schema(&target_schema, false);
|
|
let sub_res = derived.validate()?;
|
|
let is_valid = sub_res.is_valid();
|
|
result.merge(sub_res);
|
|
return Ok(is_valid);
|
|
} else {
|
|
result.errors.push(ValidationError {
|
|
code: "MISSING_COMPILED_SCHEMA".to_string(),
|
|
message: format!(
|
|
"Polymorphic router target '{}' does not exist in the database schemas map",
|
|
target_id
|
|
),
|
|
path: self.path.to_string(),
|
|
});
|
|
return Ok(false);
|
|
}
|
|
} else if let Some(idx) = idx_opt {
|
|
if let Some(target_schema) = self
|
|
.schema
|
|
.one_of
|
|
.as_ref()
|
|
.and_then(|options| options.get(*idx))
|
|
{
|
|
let derived = self.derive_for_schema(target_schema.as_ref(), false);
|
|
let sub_res = derived.validate()?;
|
|
let is_valid = sub_res.is_valid();
|
|
result.merge(sub_res);
|
|
return Ok(is_valid);
|
|
} else {
|
|
result.errors.push(ValidationError {
|
|
code: "MISSING_COMPILED_SCHEMA".to_string(),
|
|
message: format!(
|
|
"Polymorphic index target '{}' does not exist in the local oneOf array",
|
|
idx
|
|
),
|
|
path: self.path.to_string(),
|
|
});
|
|
return Ok(false);
|
|
}
|
|
} else {
|
|
return Ok(false);
|
|
}
|
|
} else {
|
|
let disc_msg = if let Some(d) = self.schema.compiled_discriminator.get() {
|
|
format!("discriminator {}='{}'", d, val)
|
|
} else {
|
|
format!("structural JSON base primitive '{}'", val)
|
|
};
|
|
result.errors.push(ValidationError {
|
|
code: if self.schema.family.is_some() {
|
|
"NO_FAMILY_MATCH".to_string()
|
|
} else {
|
|
"NO_ONEOF_MATCH".to_string()
|
|
},
|
|
message: format!(
|
|
"Payload matched no candidate boundaries based on its {}",
|
|
disc_msg
|
|
),
|
|
path: self.path.to_string(),
|
|
});
|
|
return Ok(false);
|
|
}
|
|
} else {
|
|
if let Some(d) = self.schema.compiled_discriminator.get() {
|
|
result.errors.push(ValidationError {
|
|
code: "MISSING_TYPE".to_string(),
|
|
message: format!(
|
|
"Missing explicit '{}' discriminator. Unable to resolve polymorphic boundaries",
|
|
d
|
|
),
|
|
path: self.path.to_string(),
|
|
});
|
|
}
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn validate_type_inheritance(
|
|
&self,
|
|
result: &mut ValidationResult,
|
|
) -> Result<bool, ValidationError> {
|
|
// Core inheritance logic replaces legacy routing
|
|
let payload_primitive = match self.instance {
|
|
serde_json::Value::Null => "null",
|
|
serde_json::Value::Bool(_) => "boolean",
|
|
serde_json::Value::Number(n) => {
|
|
if n.is_i64() || n.is_u64() {
|
|
"integer"
|
|
} else {
|
|
"number"
|
|
}
|
|
}
|
|
serde_json::Value::String(_) => "string",
|
|
serde_json::Value::Array(_) => "array",
|
|
serde_json::Value::Object(_) => "object",
|
|
};
|
|
|
|
let mut custom_types = Vec::new();
|
|
match &self.schema.type_ {
|
|
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
|
|
if !crate::database::object::is_primitive_type(t) {
|
|
custom_types.push(t.clone());
|
|
}
|
|
}
|
|
Some(crate::database::object::SchemaTypeOrArray::Multiple(arr)) => {
|
|
if arr.contains(&payload_primitive.to_string())
|
|
|| (payload_primitive == "integer" && arr.contains(&"number".to_string()))
|
|
{
|
|
// It natively matched a primitive in the array options, skip forcing custom proxy fallback
|
|
} else {
|
|
for t in arr {
|
|
if !crate::database::object::is_primitive_type(t) {
|
|
custom_types.push(t.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None => {}
|
|
}
|
|
|
|
for t in custom_types {
|
|
if let Some(global_schema) = self.db.get_scoped_schema(SchemaRealm::Type, &t) {
|
|
let mut new_overrides = self.overrides.clone();
|
|
if let Some(props) = &self.schema.properties {
|
|
new_overrides.extend(props.keys().map(|k| k.to_string()));
|
|
}
|
|
|
|
let mut shadow = self.derive(
|
|
&global_schema,
|
|
self.instance,
|
|
&self.path,
|
|
new_overrides,
|
|
self.extensible,
|
|
true, // Reporter mode
|
|
);
|
|
shadow.root = &global_schema;
|
|
result.merge(shadow.validate()?);
|
|
} else {
|
|
result.errors.push(ValidationError {
|
|
code: "INHERITANCE_RESOLUTION_FAILED".to_string(),
|
|
message: format!(
|
|
"Inherited entity pointer '{}' was not found in schema registry",
|
|
t
|
|
),
|
|
path: self.path.to_string(),
|
|
});
|
|
}
|
|
}
|
|
Ok(true)
|
|
}
|
|
}
|