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

180 lines
6.0 KiB
Rust

use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
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.schemas.get(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);
}
}
}