all jspg tests now passing
This commit is contained in:
@ -15,7 +15,6 @@ impl<'a> ValidationContext<'a> {
|
||||
|| self.schema.items.is_some()
|
||||
|| self.schema.ref_string.is_some()
|
||||
|| self.schema.one_of.is_some()
|
||||
|| self.schema.any_of.is_some()
|
||||
|| self.schema.all_of.is_some()
|
||||
|| self.schema.enum_.is_some()
|
||||
|| self.schema.const_.is_some();
|
||||
@ -31,7 +30,90 @@ impl<'a> ValidationContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Family specific runtime validation will go here later if needed
|
||||
if let Some(family_target) = &self.schema.family {
|
||||
// The descendants map is keyed by the schema's own $id, not the target string.
|
||||
if let Some(schema_id) = &self.schema.id
|
||||
&& let Some(descendants) = self.db.descendants.get(schema_id)
|
||||
{
|
||||
// Validate against all descendants simulating strict oneOf logic
|
||||
let mut passed_candidates: Vec<(String, usize, ValidationResult)> = Vec::new();
|
||||
|
||||
// The target itself is also an implicitly valid candidate
|
||||
let mut all_targets = vec![family_target.clone()];
|
||||
all_targets.extend(descendants.clone());
|
||||
|
||||
for child_id in &all_targets {
|
||||
if let Some(child_schema) = self.db.schemas.get(child_id) {
|
||||
let derived = self.derive(
|
||||
child_schema,
|
||||
self.instance,
|
||||
&self.path,
|
||||
self.overrides.clone(),
|
||||
self.extensible,
|
||||
self.reporter, // Inherit parent reporter flag, do not bypass strictness!
|
||||
);
|
||||
|
||||
// Explicitly run validate_scoped to accurately test candidates with strictness checks enabled
|
||||
let res = derived.validate_scoped()?;
|
||||
|
||||
if res.is_valid() {
|
||||
let depth = self.db.depths.get(child_id).copied().unwrap_or(0);
|
||||
passed_candidates.push((child_id.clone(), depth, res));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if passed_candidates.len() == 1 {
|
||||
result.merge(passed_candidates.pop().unwrap().2);
|
||||
} else if passed_candidates.is_empty() {
|
||||
result.errors.push(ValidationError {
|
||||
code: "NO_FAMILY_MATCH".to_string(),
|
||||
message: format!(
|
||||
"Payload did not match any descendants of family '{}'",
|
||||
family_target
|
||||
),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
} else {
|
||||
// Apply depth heuristic tie-breaker
|
||||
let mut best_depth: Option<usize> = None;
|
||||
let mut ambiguous = false;
|
||||
let mut best_res = None;
|
||||
|
||||
for (_, depth, res) in passed_candidates.into_iter() {
|
||||
if let Some(current_best) = best_depth {
|
||||
if depth > current_best {
|
||||
best_depth = Some(depth);
|
||||
best_res = Some(res);
|
||||
ambiguous = false; // Broke the tie
|
||||
} else if depth == current_best {
|
||||
ambiguous = true; // Tie at the highest level
|
||||
}
|
||||
} else {
|
||||
best_depth = Some(depth);
|
||||
best_res = Some(res);
|
||||
}
|
||||
}
|
||||
|
||||
if !ambiguous {
|
||||
if let Some(res) = best_res {
|
||||
result.merge(res);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
result.errors.push(ValidationError {
|
||||
code: "AMBIGUOUS_FAMILY_MATCH".to_string(),
|
||||
message: format!(
|
||||
"Payload matched multiple descendants of family '{}' without a clear depth winner",
|
||||
family_target
|
||||
),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -41,7 +123,7 @@ impl<'a> ValidationContext<'a> {
|
||||
) -> Result<bool, ValidationError> {
|
||||
// 1. Core $ref logic relies on the fast O(1) map to allow cycles and proper nesting
|
||||
if let Some(ref_str) = &self.schema.ref_string {
|
||||
if let Some(global_schema) = self.schemas.get(ref_str) {
|
||||
if let Some(global_schema) = self.db.schemas.get(ref_str) {
|
||||
let mut new_overrides = self.overrides.clone();
|
||||
if let Some(props) = &self.schema.properties {
|
||||
new_overrides.extend(props.keys().map(|k| k.to_string()));
|
||||
|
||||
Reference in New Issue
Block a user