significantly simplified the validator and work continues on query
This commit is contained in:
@ -1 +1 @@
|
||||
::pgrx::pgrx_embed!();
|
||||
::pgrx::pgrx_embed!();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,6 @@ pub struct Database {
|
||||
pub types: HashMap<String, Type>,
|
||||
pub puncs: HashMap<String, Punc>,
|
||||
pub schemas: HashMap<String, Schema>,
|
||||
pub descendants: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
@ -26,7 +25,6 @@ impl Database {
|
||||
types: HashMap::new(),
|
||||
puncs: HashMap::new(),
|
||||
schemas: HashMap::new(),
|
||||
descendants: HashMap::new(),
|
||||
};
|
||||
|
||||
if let Some(arr) = val.get("enums").and_then(|v| v.as_array()) {
|
||||
@ -75,146 +73,137 @@ impl Database {
|
||||
fn compile(&mut self) -> Result<(), String> {
|
||||
self.collect_schemas();
|
||||
|
||||
// 1. Compile regex and formats sequentially
|
||||
for schema in self.schemas.values_mut() {
|
||||
schema.compile();
|
||||
// 1. Build a structural descendant graph for $family macro expansion
|
||||
let mut direct_refs: std::collections::HashMap<String, Vec<String>> =
|
||||
std::collections::HashMap::new();
|
||||
for (id, schema) in &self.schemas {
|
||||
if let Some(ref_str) = &schema.obj.ref_string {
|
||||
direct_refs
|
||||
.entry(ref_str.clone())
|
||||
.or_default()
|
||||
.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Compute the Unified Semantic Graph (descendants)
|
||||
self.collect_descendents();
|
||||
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
|
||||
|
||||
// 3. For any schema representing a Postgres table, cache its allowed subclasses
|
||||
self.compile_allowed_types();
|
||||
// 2. Expand $family macros into oneOf blocks
|
||||
for id in &schema_ids {
|
||||
if let Some(schema) = self.schemas.get_mut(id) {
|
||||
schema.map_children(|mut child| {
|
||||
Self::expand_family(&mut child, &direct_refs);
|
||||
});
|
||||
Self::expand_family(schema, &direct_refs);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Finally, securely link all string $refs into memory pointers (Arc)
|
||||
self.compile_pointers();
|
||||
let schemas_snap = self.schemas.clone();
|
||||
|
||||
// 3. Compile internals and link memory pointers
|
||||
for id in schema_ids {
|
||||
if let Some(schema) = self.schemas.get_mut(&id) {
|
||||
schema.compile_internals();
|
||||
schema.link_refs(&schemas_snap);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_schemas(&mut self) {
|
||||
let mut to_insert = Vec::new();
|
||||
for (_, type_def) in &self.types {
|
||||
for schema in &type_def.schemas {
|
||||
if let Some(id) = &schema.obj.id {
|
||||
to_insert.push((id.clone(), schema.clone()));
|
||||
}
|
||||
|
||||
// Pass A: Entities - Compute Variations from hierarchies
|
||||
// `hierarchy` is an array of ancestors. E.g. `person` -> `['entity', 'user', 'person']`.
|
||||
// We map this backward so that `user`'s allowed variations = `['user', 'person']`.
|
||||
let mut variations_by_entity = std::collections::HashMap::new();
|
||||
for type_def in self.types.values() {
|
||||
for ancestor in &type_def.hierarchy {
|
||||
variations_by_entity
|
||||
.entry(ancestor.clone())
|
||||
.or_insert_with(std::collections::HashSet::new)
|
||||
.insert(type_def.name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Now stamp all exported entity schemas with their precise physical variations
|
||||
for (_, type_def) in &self.types {
|
||||
let allowed_strings = variations_by_entity
|
||||
.get(&type_def.name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
for mut schema in type_def.schemas.clone() {
|
||||
schema.stamp_variations(Some(allowed_strings.clone()));
|
||||
schema.harvest(&mut to_insert);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass B: APIs and Enums (No initial variations stamped)
|
||||
for (_, punc_def) in &self.puncs {
|
||||
for schema in &punc_def.schemas {
|
||||
if let Some(id) = &schema.obj.id {
|
||||
to_insert.push((id.clone(), schema.clone()));
|
||||
}
|
||||
for mut schema in punc_def.schemas.clone() {
|
||||
schema.harvest(&mut to_insert);
|
||||
}
|
||||
}
|
||||
for (_, enum_def) in &self.enums {
|
||||
for schema in &enum_def.schemas {
|
||||
if let Some(id) = &schema.obj.id {
|
||||
to_insert.push((id.clone(), schema.clone()));
|
||||
}
|
||||
for mut schema in enum_def.schemas.clone() {
|
||||
schema.harvest(&mut to_insert);
|
||||
}
|
||||
}
|
||||
|
||||
for (id, schema) in to_insert {
|
||||
self.schemas.insert(id, schema);
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_descendents(&mut self) {
|
||||
let mut direct_children: HashMap<String, Vec<String>> = HashMap::new();
|
||||
fn expand_family(
|
||||
schema: &mut crate::database::schema::Schema,
|
||||
direct_refs: &std::collections::HashMap<String, Vec<String>>,
|
||||
) {
|
||||
if let Some(family_target) = &schema.obj.family {
|
||||
let mut descendants = std::collections::HashSet::new();
|
||||
Self::collect_descendants(family_target, direct_refs, &mut descendants);
|
||||
|
||||
// First pass: Find all schemas that have a $ref to another schema
|
||||
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
|
||||
for id in schema_ids {
|
||||
if let Some(ref_str) = self.schemas.get(&id).and_then(|s| s.obj.ref_string.clone()) {
|
||||
if self.schemas.contains_key(&ref_str) {
|
||||
direct_children.entry(ref_str).or_default().push(id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
// the "$family" macro is logically replaced by an anyOf of its descendants + itself
|
||||
let mut derived_any_of = Vec::new();
|
||||
|
||||
// Now compute descendants for all schemas
|
||||
let mut descendants_map: HashMap<String, Vec<String>> = HashMap::new();
|
||||
for key in self.schemas.keys() {
|
||||
let mut descendants = Vec::new();
|
||||
let mut queue = Vec::new();
|
||||
if let Some(children) = direct_children.get(key) {
|
||||
queue.extend(children.iter().cloned());
|
||||
// Include the target base itself if valid (which it always is structurally)
|
||||
let mut base_ref = crate::database::schema::SchemaObject::default();
|
||||
base_ref.ref_string = Some(family_target.clone());
|
||||
derived_any_of.push(std::sync::Arc::new(crate::database::schema::Schema {
|
||||
obj: base_ref,
|
||||
always_fail: false,
|
||||
}));
|
||||
|
||||
// Sort descendants for determinism during testing
|
||||
let mut desc_vec: Vec<String> = descendants.into_iter().collect();
|
||||
desc_vec.sort();
|
||||
|
||||
for child_id in desc_vec {
|
||||
let mut child_ref = crate::database::schema::SchemaObject::default();
|
||||
child_ref.ref_string = Some(child_id);
|
||||
derived_any_of.push(std::sync::Arc::new(crate::database::schema::Schema {
|
||||
obj: child_ref,
|
||||
always_fail: false,
|
||||
}));
|
||||
}
|
||||
|
||||
let mut visited = std::collections::HashSet::new();
|
||||
while let Some(child) = queue.pop() {
|
||||
if visited.insert(child.clone()) {
|
||||
descendants.push(child.clone());
|
||||
if let Some(grandchildren) = direct_children.get(&child) {
|
||||
queue.extend(grandchildren.iter().cloned());
|
||||
}
|
||||
}
|
||||
}
|
||||
descendants_map.insert(key.clone(), descendants);
|
||||
}
|
||||
self.descendants = descendants_map;
|
||||
}
|
||||
|
||||
fn compile_allowed_types(&mut self) {
|
||||
// 1. Identify which types act as bases (table-backed schemas)
|
||||
let mut entity_bases = HashMap::new();
|
||||
for type_def in self.types.values() {
|
||||
for type_schema in &type_def.schemas {
|
||||
if let Some(id) = &type_schema.obj.id {
|
||||
entity_bases.insert(id.clone(), type_def.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Compute compiled_allowed_types for all descendants of entity bases
|
||||
let mut allowed_types_map: HashMap<String, std::collections::HashSet<String>> = HashMap::new();
|
||||
for base_id in entity_bases.keys() {
|
||||
allowed_types_map.insert(
|
||||
base_id.clone(),
|
||||
self
|
||||
.descendants
|
||||
.get(base_id)
|
||||
.unwrap_or(&vec![])
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
);
|
||||
if let Some(descendants) = self.descendants.get(base_id) {
|
||||
let set: std::collections::HashSet<String> = descendants.iter().cloned().collect();
|
||||
for desc_id in descendants {
|
||||
allowed_types_map.insert(desc_id.clone(), set.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Inject types into the schemas
|
||||
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
|
||||
for id in schema_ids {
|
||||
if let Some(set) = allowed_types_map.get(&id) {
|
||||
if let Some(schema) = self.schemas.get_mut(&id) {
|
||||
schema.obj.compiled_allowed_types = Some(set.clone());
|
||||
}
|
||||
}
|
||||
schema.obj.any_of = Some(derived_any_of);
|
||||
// Remove family so it doesn't cause conflicts or fail the simple validation
|
||||
schema.obj.family = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_pointers(&mut self) {
|
||||
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
|
||||
for id in schema_ids {
|
||||
let mut compiled_ref = None;
|
||||
|
||||
if let Some(schema) = self.schemas.get(&id) {
|
||||
if let Some(ref_str) = &schema.obj.ref_string {
|
||||
if let Some(target) = self.schemas.get(ref_str) {
|
||||
compiled_ref = Some(std::sync::Arc::new(target.clone()));
|
||||
}
|
||||
fn collect_descendants(
|
||||
target: &str,
|
||||
direct_refs: &std::collections::HashMap<String, Vec<String>>,
|
||||
descendants: &mut std::collections::HashSet<String>,
|
||||
) {
|
||||
if let Some(children) = direct_refs.get(target) {
|
||||
for child in children {
|
||||
if descendants.insert(child.clone()) {
|
||||
Self::collect_descendants(child, direct_refs, descendants);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(schema) = self.schemas.get_mut(&id) {
|
||||
schema.obj.compiled_ref = compiled_ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,7 +124,7 @@ pub struct SchemaObject {
|
||||
#[serde(skip)]
|
||||
pub compiled_ref: Option<Arc<Schema>>,
|
||||
#[serde(skip)]
|
||||
pub compiled_allowed_types: Option<std::collections::HashSet<String>>,
|
||||
pub compiled_variations: Option<std::collections::HashSet<String>>,
|
||||
#[serde(skip)]
|
||||
pub compiled_format: Option<CompiledFormat>,
|
||||
#[serde(skip)]
|
||||
@ -133,11 +133,6 @@ pub struct SchemaObject {
|
||||
pub compiled_pattern_properties: Option<Vec<(CompiledRegex, Arc<Schema>)>>,
|
||||
}
|
||||
|
||||
pub enum ResolvedRef<'a> {
|
||||
Local(&'a Schema),
|
||||
Global(&'a Schema, &'a Schema),
|
||||
}
|
||||
|
||||
/// Represents a compiled format validator
|
||||
#[derive(Clone)]
|
||||
pub enum CompiledFormat {
|
||||
@ -188,12 +183,9 @@ impl std::ops::DerefMut for Schema {
|
||||
}
|
||||
|
||||
impl Schema {
|
||||
pub fn resolve_ref(&self, _ref_string: &str) -> Option<&Arc<Schema>> {
|
||||
// This is vestigial for now. References are global pointers. We will remove this shortly.
|
||||
None
|
||||
}
|
||||
pub fn compile_internals(&mut self) {
|
||||
self.map_children(|child| child.compile_internals());
|
||||
|
||||
pub fn compile(&mut self) {
|
||||
if let Some(format_str) = &self.obj.format {
|
||||
if let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) {
|
||||
self.obj.compiled_format = Some(crate::database::schema::CompiledFormat::Func(fmt.func));
|
||||
@ -217,96 +209,112 @@ impl Schema {
|
||||
self.obj.compiled_pattern_properties = Some(compiled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Crawl children recursively to compile their internals
|
||||
pub fn link_refs(&mut self, schemas: &std::collections::HashMap<String, Schema>) {
|
||||
if let Some(ref_str) = &self.obj.ref_string {
|
||||
if let Some(target) = schemas.get(ref_str) {
|
||||
self.obj.compiled_ref = Some(Arc::new(target.clone()));
|
||||
|
||||
// Viral Infection: Inherit physical entity boundaries across the $ref pointer recursively
|
||||
if self.obj.compiled_variations.is_none() {
|
||||
let mut visited = std::collections::HashSet::new();
|
||||
self.obj.compiled_variations = Self::resolve_variations(ref_str, schemas, &mut visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.map_children(|child| child.link_refs(schemas));
|
||||
}
|
||||
|
||||
fn resolve_variations(
|
||||
ref_str: &str,
|
||||
schemas: &std::collections::HashMap<String, Schema>,
|
||||
visited: &mut std::collections::HashSet<String>,
|
||||
) -> Option<std::collections::HashSet<String>> {
|
||||
if !visited.insert(ref_str.to_string()) {
|
||||
return None; // Cycle detected
|
||||
}
|
||||
|
||||
if let Some(target) = schemas.get(ref_str) {
|
||||
if let Some(vars) = &target.obj.compiled_variations {
|
||||
return Some(vars.clone());
|
||||
}
|
||||
if let Some(next_ref) = &target.obj.ref_string {
|
||||
return Self::resolve_variations(next_ref, schemas, visited);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn stamp_variations(&mut self, variations: Option<std::collections::HashSet<String>>) {
|
||||
self.obj.compiled_variations = variations.clone();
|
||||
self.map_children(|child| child.stamp_variations(variations.clone()));
|
||||
}
|
||||
|
||||
pub fn harvest(&mut self, to_insert: &mut Vec<(String, Schema)>) {
|
||||
if let Some(id) = &self.obj.id {
|
||||
to_insert.push((id.clone(), self.clone()));
|
||||
}
|
||||
self.map_children(|child| child.harvest(to_insert));
|
||||
}
|
||||
|
||||
pub fn map_children<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(&mut Schema),
|
||||
{
|
||||
if let Some(props) = &mut self.obj.properties {
|
||||
for (_, v) in props {
|
||||
// Safe deep mutation workaround without unsafe Arc unwrap
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
f(&mut inner);
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pattern_props) = &mut self.obj.pattern_properties {
|
||||
for (_, v) in pattern_props {
|
||||
let mut inner = (**v).clone();
|
||||
f(&mut inner);
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
}
|
||||
|
||||
let mut map_arr = |arr: &mut Vec<Arc<Schema>>| {
|
||||
for v in arr.iter_mut() {
|
||||
let mut inner = (**v).clone();
|
||||
f(&mut inner);
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(arr) = &mut self.obj.prefix_items {
|
||||
for v in arr.iter_mut() {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
map_arr(arr);
|
||||
}
|
||||
|
||||
if let Some(arr) = &mut self.obj.all_of {
|
||||
for v in arr.iter_mut() {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
map_arr(arr);
|
||||
}
|
||||
|
||||
if let Some(arr) = &mut self.obj.any_of {
|
||||
for v in arr.iter_mut() {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
map_arr(arr);
|
||||
}
|
||||
|
||||
if let Some(arr) = &mut self.obj.one_of {
|
||||
for v in arr.iter_mut() {
|
||||
map_arr(arr);
|
||||
}
|
||||
|
||||
let mut map_opt = |opt: &mut Option<Arc<Schema>>| {
|
||||
if let Some(v) = opt {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
f(&mut inner);
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(v) = &mut self.obj.additional_properties {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
|
||||
if let Some(v) = &mut self.obj.items {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
|
||||
if let Some(v) = &mut self.obj.contains {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
|
||||
if let Some(v) = &mut self.obj.property_names {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
|
||||
if let Some(v) = &mut self.obj.not {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
|
||||
if let Some(v) = &mut self.obj.if_ {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
|
||||
if let Some(v) = &mut self.obj.then_ {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
|
||||
if let Some(v) = &mut self.obj.else_ {
|
||||
let mut inner = (**v).clone();
|
||||
inner.compile();
|
||||
*v = Arc::new(inner);
|
||||
}
|
||||
map_opt(&mut self.obj.additional_properties);
|
||||
map_opt(&mut self.obj.items);
|
||||
map_opt(&mut self.obj.contains);
|
||||
map_opt(&mut self.obj.property_names);
|
||||
map_opt(&mut self.obj.not);
|
||||
map_opt(&mut self.obj.if_);
|
||||
map_opt(&mut self.obj.then_);
|
||||
map_opt(&mut self.obj.else_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,7 +335,37 @@ impl<'de> Deserialize<'de> for Schema {
|
||||
always_fail: !b,
|
||||
});
|
||||
}
|
||||
let obj: SchemaObject = serde_json::from_value(v.clone()).map_err(serde::de::Error::custom)?;
|
||||
let mut obj: SchemaObject =
|
||||
serde_json::from_value(v.clone()).map_err(serde::de::Error::custom)?;
|
||||
|
||||
// If a schema is effectively empty (except for potentially carrying an ID),
|
||||
// it functions as a boolean `true` schema in Draft2020 which means it should not
|
||||
// restrict additional properties natively
|
||||
let is_empty = obj.type_.is_none()
|
||||
&& obj.properties.is_none()
|
||||
&& obj.pattern_properties.is_none()
|
||||
&& obj.additional_properties.is_none()
|
||||
&& obj.required.is_none()
|
||||
&& obj.dependencies.is_none()
|
||||
&& obj.items.is_none()
|
||||
&& obj.prefix_items.is_none()
|
||||
&& obj.contains.is_none()
|
||||
&& obj.format.is_none()
|
||||
&& obj.enum_.is_none()
|
||||
&& obj.const_.is_none()
|
||||
&& obj.all_of.is_none()
|
||||
&& obj.any_of.is_none()
|
||||
&& obj.one_of.is_none()
|
||||
&& obj.not.is_none()
|
||||
&& obj.if_.is_none()
|
||||
&& obj.then_.is_none()
|
||||
&& obj.else_.is_none()
|
||||
&& obj.ref_string.is_none()
|
||||
&& obj.family.is_none();
|
||||
|
||||
if is_empty && obj.extensible.is_none() {
|
||||
obj.extensible = Some(true);
|
||||
}
|
||||
|
||||
Ok(Schema {
|
||||
obj,
|
||||
|
||||
@ -101,6 +101,72 @@ fn test_additional_properties_2() {
|
||||
crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_0() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_1() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_2() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_3() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 3).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_4() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 4).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_5() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 5).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_6() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 6).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_7() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 7).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_8() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 8).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_9() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 9).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependencies_10() {
|
||||
let path = format!("{}/tests/fixtures/dependencies.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 10).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_exclusive_minimum_0() {
|
||||
let path = format!("{}/tests/fixtures/exclusiveMinimum.json", env!("CARGO_MANIFEST_DIR"));
|
||||
@ -497,30 +563,6 @@ fn test_items_15() {
|
||||
crate::validator::util::run_test_file_at_index(&path, 15).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_typed_refs_0() {
|
||||
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_typed_refs_1() {
|
||||
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_typed_refs_2() {
|
||||
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_typed_refs_3() {
|
||||
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 3).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_enum_0() {
|
||||
let path = format!("{}/tests/fixtures/enum.json", env!("CARGO_MANIFEST_DIR"));
|
||||
@ -803,42 +845,6 @@ fn test_max_length_1() {
|
||||
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_schemas_0() {
|
||||
let path = format!("{}/tests/fixtures/dependentSchemas.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_schemas_1() {
|
||||
let path = format!("{}/tests/fixtures/dependentSchemas.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_schemas_2() {
|
||||
let path = format!("{}/tests/fixtures/dependentSchemas.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_schemas_3() {
|
||||
let path = format!("{}/tests/fixtures/dependentSchemas.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 3).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_schemas_4() {
|
||||
let path = format!("{}/tests/fixtures/dependentSchemas.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 4).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_schemas_5() {
|
||||
let path = format!("{}/tests/fixtures/dependentSchemas.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 5).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_exclusive_maximum_0() {
|
||||
let path = format!("{}/tests/fixtures/exclusiveMaximum.json", env!("CARGO_MANIFEST_DIR"));
|
||||
@ -1079,30 +1085,6 @@ fn test_pattern_1() {
|
||||
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_masking_0() {
|
||||
let path = format!("{}/tests/fixtures/masking.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_masking_1() {
|
||||
let path = format!("{}/tests/fixtures/masking.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_masking_2() {
|
||||
let path = format!("{}/tests/fixtures/masking.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_masking_3() {
|
||||
let path = format!("{}/tests/fixtures/masking.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 3).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_max_properties_0() {
|
||||
let path = format!("{}/tests/fixtures/maxProperties.json", env!("CARGO_MANIFEST_DIR"));
|
||||
@ -1127,36 +1109,6 @@ fn test_max_properties_3() {
|
||||
crate::validator::util::run_test_file_at_index(&path, 3).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_required_0() {
|
||||
let path = format!("{}/tests/fixtures/dependentRequired.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_required_1() {
|
||||
let path = format!("{}/tests/fixtures/dependentRequired.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_required_2() {
|
||||
let path = format!("{}/tests/fixtures/dependentRequired.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_required_3() {
|
||||
let path = format!("{}/tests/fixtures/dependentRequired.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 3).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_dependent_required_4() {
|
||||
let path = format!("{}/tests/fixtures/dependentRequired.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 4).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_required_0() {
|
||||
let path = format!("{}/tests/fixtures/required.json", env!("CARGO_MANIFEST_DIR"));
|
||||
@ -1685,150 +1637,6 @@ fn test_ref_15() {
|
||||
crate::validator::util::run_test_file_at_index(&path, 15).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_16() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 16).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_17() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 17).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_18() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 18).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_19() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 19).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_20() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 20).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_21() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 21).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_22() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 22).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_23() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 23).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_24() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 24).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_25() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 25).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_26() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 26).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_27() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 27).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_28() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 28).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_29() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 29).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_30() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 30).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_31() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 31).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_32() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 32).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_33() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 33).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_34() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 34).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_35() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 35).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_36() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 36).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_37() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 37).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_38() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 38).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_ref_39() {
|
||||
let path = format!("{}/tests/fixtures/ref.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, 39).unwrap();
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
fn test_maximum_0() {
|
||||
let path = format!("{}/tests/fixtures/maximum.json", env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
@ -11,39 +11,16 @@ pub struct ValidationContext<'a> {
|
||||
pub depth: usize,
|
||||
pub extensible: bool,
|
||||
pub reporter: bool,
|
||||
pub overrides: std::collections::HashSet<String>,
|
||||
}
|
||||
|
||||
impl<'a> ValidationContext<'a> {
|
||||
pub fn resolve_ref(
|
||||
&self,
|
||||
ref_string: &str,
|
||||
) -> Option<(crate::database::schema::ResolvedRef<'a>, String)> {
|
||||
if let Some(local_schema_arc) = self.root.resolve_ref(ref_string) {
|
||||
if ref_string.starts_with('#') {
|
||||
return Some((
|
||||
crate::database::schema::ResolvedRef::Local(local_schema_arc.as_ref()),
|
||||
ref_string.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// We will replace all of this with `self.schema.compiled_ref` heavily shortly.
|
||||
// For now, doing a basic map lookup to pass compilation.
|
||||
if let Some(s) = self.schemas.get(ref_string) {
|
||||
return Some((
|
||||
crate::database::schema::ResolvedRef::Global(s, s),
|
||||
ref_string.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
schemas: &'a std::collections::HashMap<String, Schema>,
|
||||
root: &'a Schema,
|
||||
schema: &'a Schema,
|
||||
instance: &'a serde_json::Value,
|
||||
overrides: std::collections::HashSet<String>,
|
||||
extensible: bool,
|
||||
reporter: bool,
|
||||
) -> Self {
|
||||
@ -57,6 +34,7 @@ impl<'a> ValidationContext<'a> {
|
||||
depth: 0,
|
||||
extensible: effective_extensible,
|
||||
reporter,
|
||||
overrides,
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +43,7 @@ impl<'a> ValidationContext<'a> {
|
||||
schema: &'a Schema,
|
||||
instance: &'a serde_json::Value,
|
||||
path: &str,
|
||||
overrides: std::collections::HashSet<String>,
|
||||
extensible: bool,
|
||||
reporter: bool,
|
||||
) -> Self {
|
||||
@ -79,11 +58,19 @@ impl<'a> ValidationContext<'a> {
|
||||
depth: self.depth + 1,
|
||||
extensible: effective_extensible,
|
||||
reporter,
|
||||
overrides,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive_for_schema(&self, schema: &'a Schema, reporter: bool) -> Self {
|
||||
self.derive(schema, self.instance, &self.path, self.extensible, reporter)
|
||||
self.derive(
|
||||
schema,
|
||||
self.instance,
|
||||
&self.path,
|
||||
std::collections::HashSet::new(),
|
||||
self.extensible,
|
||||
reporter,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<ValidationResult, ValidationError> {
|
||||
|
||||
@ -8,17 +8,16 @@ pub use context::ValidationContext;
|
||||
pub use error::ValidationError;
|
||||
pub use result::ValidationResult;
|
||||
|
||||
use crate::database::schema::Schema;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
pub struct Validator {
|
||||
pub schemas: std::sync::Arc<std::collections::HashMap<String, crate::database::schema::Schema>>,
|
||||
pub schemas: Arc<HashMap<String, Schema>>,
|
||||
}
|
||||
|
||||
impl Validator {
|
||||
pub fn new(
|
||||
schemas: std::sync::Arc<std::collections::HashMap<String, crate::database::schema::Schema>>,
|
||||
) -> Self {
|
||||
pub fn new(schemas: Arc<HashMap<String, Schema>>) -> Self {
|
||||
Self { schemas }
|
||||
}
|
||||
|
||||
@ -50,7 +49,15 @@ impl Validator {
|
||||
instance: &Value,
|
||||
) -> Result<ValidationResult, ValidationError> {
|
||||
if let Some(schema) = self.schemas.get(schema_id) {
|
||||
let ctx = ValidationContext::new(&self.schemas, schema, schema, instance, false, false);
|
||||
let ctx = ValidationContext::new(
|
||||
&self.schemas,
|
||||
schema,
|
||||
schema,
|
||||
instance,
|
||||
std::collections::HashSet::new(),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
ctx.validate_scoped()
|
||||
} else {
|
||||
Err(ValidationError {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
use serde_json::Value;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::validator::context::ValidationContext;
|
||||
use crate::validator::error::ValidationError;
|
||||
@ -53,6 +52,7 @@ impl<'a> ValidationContext<'a> {
|
||||
contains_schema,
|
||||
child_instance,
|
||||
&self.path,
|
||||
std::collections::HashSet::new(),
|
||||
self.extensible,
|
||||
false,
|
||||
);
|
||||
@ -91,7 +91,14 @@ impl<'a> ValidationContext<'a> {
|
||||
if i < len {
|
||||
let path = format!("{}/{}", self.path, i);
|
||||
if let Some(child_instance) = arr.get(i) {
|
||||
let derived = self.derive(sub_schema, child_instance, &path, self.extensible, false);
|
||||
let derived = self.derive(
|
||||
sub_schema,
|
||||
child_instance,
|
||||
&path,
|
||||
std::collections::HashSet::new(),
|
||||
self.extensible,
|
||||
false,
|
||||
);
|
||||
let item_res = derived.validate()?;
|
||||
result.merge(item_res);
|
||||
result.evaluated_indices.insert(i);
|
||||
@ -105,7 +112,14 @@ impl<'a> ValidationContext<'a> {
|
||||
for i in validation_index..len {
|
||||
let path = format!("{}/{}", self.path, i);
|
||||
if let Some(child_instance) = arr.get(i) {
|
||||
let derived = self.derive(items_schema, child_instance, &path, self.extensible, false);
|
||||
let derived = self.derive(
|
||||
items_schema,
|
||||
child_instance,
|
||||
&path,
|
||||
std::collections::HashSet::new(),
|
||||
self.extensible,
|
||||
false,
|
||||
);
|
||||
let item_res = derived.validate()?;
|
||||
result.merge(item_res);
|
||||
result.evaluated_indices.insert(i);
|
||||
|
||||
@ -42,7 +42,7 @@ impl<'a> ValidationContext<'a> {
|
||||
|
||||
if let Some(obj) = self.instance.as_object() {
|
||||
for key in obj.keys() {
|
||||
if !result.evaluated_keys.contains(key) {
|
||||
if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) {
|
||||
result.errors.push(ValidationError {
|
||||
code: "STRICT_PROPERTY_VIOLATION".to_string(),
|
||||
message: format!("Unexpected property '{}'", key),
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
use serde_json::Value;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::validator::context::ValidationContext;
|
||||
use crate::validator::error::ValidationError;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
use serde_json::Value;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::validator::context::ValidationContext;
|
||||
use crate::validator::error::ValidationError;
|
||||
@ -13,7 +12,7 @@ impl<'a> ValidationContext<'a> {
|
||||
let current = self.instance;
|
||||
if let Some(obj) = current.as_object() {
|
||||
// Entity Bound Implicit Type Validation
|
||||
if let Some(allowed_types) = &self.schema.obj.compiled_allowed_types {
|
||||
if let Some(allowed_types) = &self.schema.obj.compiled_variations {
|
||||
if let Some(type_val) = obj.get("type") {
|
||||
if let Some(type_str) = type_val.as_str() {
|
||||
if allowed_types.contains(type_str) {
|
||||
@ -62,8 +61,38 @@ impl<'a> ValidationContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
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 = format!("{}/{}", self.path, key);
|
||||
let is_ref = sub_schema.ref_string.is_some() || sub_schema.obj.compiled_ref.is_some();
|
||||
@ -73,6 +102,7 @@ impl<'a> ValidationContext<'a> {
|
||||
sub_schema,
|
||||
child_instance,
|
||||
&new_path,
|
||||
std::collections::HashSet::new(),
|
||||
next_extensible,
|
||||
false,
|
||||
);
|
||||
@ -80,7 +110,7 @@ impl<'a> ValidationContext<'a> {
|
||||
|
||||
// Entity Bound Implicit Type Interception
|
||||
if key == "type" {
|
||||
if let Some(allowed_types) = &self.schema.obj.compiled_allowed_types {
|
||||
if let Some(allowed_types) = &self.schema.obj.compiled_variations {
|
||||
if let Some(instance_type) = child_instance.as_str() {
|
||||
if allowed_types.contains(instance_type) {
|
||||
item_res
|
||||
@ -109,6 +139,7 @@ impl<'a> ValidationContext<'a> {
|
||||
sub_schema,
|
||||
child_instance,
|
||||
&new_path,
|
||||
std::collections::HashSet::new(),
|
||||
next_extensible,
|
||||
false,
|
||||
);
|
||||
@ -149,6 +180,7 @@ impl<'a> ValidationContext<'a> {
|
||||
additional_schema,
|
||||
child_instance,
|
||||
&new_path,
|
||||
std::collections::HashSet::new(),
|
||||
next_extensible,
|
||||
false,
|
||||
);
|
||||
@ -169,6 +201,7 @@ impl<'a> ValidationContext<'a> {
|
||||
self.root,
|
||||
property_names,
|
||||
&val_str,
|
||||
std::collections::HashSet::new(),
|
||||
self.extensible,
|
||||
self.reporter,
|
||||
);
|
||||
|
||||
@ -39,22 +39,31 @@ impl<'a> ValidationContext<'a> {
|
||||
&self,
|
||||
result: &mut ValidationResult,
|
||||
) -> Result<bool, ValidationError> {
|
||||
// 1. Core $ref logic fully transitioned to memory pointer resolutions.
|
||||
if let Some(_ref_str) = &self.schema.ref_string {
|
||||
if let Some(global_schema) = &self.schema.compiled_ref {
|
||||
// 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) {
|
||||
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,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
shadow.root = global_schema;
|
||||
result.merge(shadow.validate()?);
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
code: "REF_RESOLUTION_FAILED".to_string(),
|
||||
message: format!("Reference pointer was not compiled inside Database graph"),
|
||||
message: format!(
|
||||
"Reference pointer to '{}' was not found in schema registry",
|
||||
ref_str
|
||||
),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ struct TestCase {
|
||||
data: serde_json::Value,
|
||||
valid: bool,
|
||||
// Support explicit schema ID target for test case
|
||||
schema_id: Option<String>,
|
||||
schema_id: String,
|
||||
}
|
||||
|
||||
// use crate::validator::registry::REGISTRY; // No longer used directly for tests!
|
||||
@ -49,62 +49,36 @@ pub fn run_test_file_at_index(path: &str, index: usize) -> Result<(), String> {
|
||||
|
||||
// 4. Run Tests
|
||||
for (_test_index, test) in group.tests.iter().enumerate() {
|
||||
let mut schema_id = test.schema_id.clone();
|
||||
let schema_id = &test.schema_id;
|
||||
|
||||
// If no explicit schema_id, infer from the database structure
|
||||
if schema_id.is_none() {
|
||||
if let Some(schemas) = db_json.get("schemas").and_then(|v| v.as_array()) {
|
||||
if let Some(first) = schemas.first() {
|
||||
if let Some(id) = first.get("$id").and_then(|v| v.as_str()) {
|
||||
schema_id = Some(id.to_string());
|
||||
} else {
|
||||
schema_id = Some("schema_0".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if schema_id.is_none() {
|
||||
if let Some(puncs) = db_json.get("puncs").and_then(|v| v.as_array()) {
|
||||
if let Some(first_punc) = puncs.first() {
|
||||
if let Some(schemas) = first_punc.get("schemas").and_then(|v| v.as_array()) {
|
||||
if let Some(first) = schemas.first() {
|
||||
if let Some(id) = first.get("$id").and_then(|v| v.as_str()) {
|
||||
schema_id = Some(id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if validator.schemas.get(schema_id).is_none() {
|
||||
failures.push(format!(
|
||||
"[{}] Missing Schema: Cannot find schema ID '{}'",
|
||||
group.description, schema_id
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(sid) = schema_id {
|
||||
let result = validator.validate(&sid, &test.data);
|
||||
let result = validator.validate(schema_id, &test.data);
|
||||
|
||||
let (got_valid, _errors) = match &result {
|
||||
Ok(res) => (res.is_valid(), &res.errors),
|
||||
Err(_e) => {
|
||||
// If we encounter an execution error (e.g. Schema Not Found),
|
||||
// we treat it as a test failure.
|
||||
(false, &vec![])
|
||||
}
|
||||
let (got_valid, _errors) = match &result {
|
||||
Ok(res) => (res.is_valid(), &res.errors),
|
||||
Err(_e) => {
|
||||
// If we encounter an execution error (e.g. Schema Not Found),
|
||||
// we treat it as a test failure.
|
||||
(false, &vec![])
|
||||
}
|
||||
};
|
||||
|
||||
if got_valid != test.valid {
|
||||
let error_msg = match &result {
|
||||
Ok(res) => format!("{:?}", res.errors),
|
||||
Err(e) => format!("Execution Error: {:?}", e),
|
||||
};
|
||||
|
||||
if got_valid != test.valid {
|
||||
let error_msg = match &result {
|
||||
Ok(res) => format!("{:?}", res.errors),
|
||||
Err(e) => format!("Execution Error: {:?}", e),
|
||||
};
|
||||
|
||||
failures.push(format!(
|
||||
"[{}] Test '{}' failed. Expected: {}, Got: {}. Errors: {}",
|
||||
group.description, test.description, test.valid, got_valid, error_msg
|
||||
));
|
||||
}
|
||||
} else {
|
||||
failures.push(format!(
|
||||
"[{}] Test '{}' skipped: No schema ID found.",
|
||||
group.description, test.description
|
||||
"[{}] Test '{}' failed. Expected: {}, Got: {}. Errors: {}",
|
||||
group.description, test.description, test.valid, got_valid, error_msg
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user