added realm to jspg processing
This commit is contained in:
@ -52,7 +52,7 @@ impl Schema {
|
||||
// 1. Resolve INHERITANCE dependencies first
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
if let Some(parent) = db.schemas.get(t) {
|
||||
if let Some(parent) = db.get_scoped_schema(crate::database::realm::SchemaRealm::Type, t) {
|
||||
parent.as_ref().compile(db, t, t.clone(), errors);
|
||||
if let Some(p_props) = parent.obj.compiled_properties.get() {
|
||||
props.extend(p_props.clone());
|
||||
@ -86,7 +86,7 @@ impl Schema {
|
||||
|
||||
for t in types {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
if let Some(parent) = db.schemas.get(t) {
|
||||
if let Some(parent) = db.get_scoped_schema(crate::database::realm::SchemaRealm::Type, t) {
|
||||
parent.as_ref().compile(db, t, t.clone(), errors);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ impl Schema {
|
||||
format!("{}.{}", family_prefix, var)
|
||||
};
|
||||
|
||||
if db.schemas.contains_key(&target_id) {
|
||||
if db.get_scoped_schema(crate::database::realm::SchemaRealm::Type, &target_id).is_some() {
|
||||
options.insert(var.to_string(), (None, Some(target_id)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,12 +6,11 @@ pub mod formats;
|
||||
pub mod object;
|
||||
pub mod page;
|
||||
pub mod punc;
|
||||
pub mod realm;
|
||||
pub mod relation;
|
||||
pub mod schema;
|
||||
pub mod r#type;
|
||||
|
||||
// External mock exports inside the executor sub-folder
|
||||
|
||||
use r#enum::Enum;
|
||||
use executors::DatabaseExecutor;
|
||||
|
||||
@ -22,6 +21,7 @@ use executors::pgrx::SpiExecutor;
|
||||
use executors::mock::MockExecutor;
|
||||
|
||||
use punc::Punc;
|
||||
use realm::SchemaRealm;
|
||||
use relation::Relation;
|
||||
use schema::Schema;
|
||||
use serde_json::Value;
|
||||
@ -36,8 +36,6 @@ pub struct Database {
|
||||
pub puncs: HashMap<String, Punc>,
|
||||
pub relations: HashMap<String, Relation>,
|
||||
#[serde(skip)]
|
||||
pub schemas: HashMap<String, Arc<Schema>>,
|
||||
#[serde(skip)]
|
||||
pub executor: Box<dyn DatabaseExecutor + Send + Sync>,
|
||||
}
|
||||
|
||||
@ -48,7 +46,6 @@ impl Database {
|
||||
types: HashMap::new(),
|
||||
relations: HashMap::new(),
|
||||
puncs: HashMap::new(),
|
||||
schemas: HashMap::new(),
|
||||
#[cfg(not(test))]
|
||||
executor: Box::new(SpiExecutor::new()),
|
||||
#[cfg(test)]
|
||||
@ -157,26 +154,6 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(map) = val.get("schemas").and_then(|v| v.as_object()) {
|
||||
for (key, item) in map.iter() {
|
||||
match serde_json::from_value::<Schema>(item.clone()) {
|
||||
Ok(schema) => {
|
||||
db.schemas.insert(key.clone(), Arc::new(schema));
|
||||
}
|
||||
Err(e) => {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "DATABASE_SCHEMA_PARSE_FAILED".to_string(),
|
||||
message: format!("Failed to parse database schema key '{}': {}", key, e),
|
||||
details: crate::drop::ErrorDetails {
|
||||
context: Some(serde_json::json!(key)),
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.compile(&mut errors);
|
||||
let drop = if errors.is_empty() {
|
||||
crate::drop::Drop::success()
|
||||
@ -213,30 +190,26 @@ impl Database {
|
||||
}
|
||||
|
||||
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
|
||||
// Collect existing schemas patched in the databse
|
||||
let mut harvested = Vec::new();
|
||||
for (id, schema_arc) in &self.schemas {
|
||||
crate::database::schema::Schema::collect_schemas(
|
||||
schema_arc,
|
||||
id,
|
||||
id.clone(),
|
||||
&mut harvested,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
for (id, schema_arc) in harvested {
|
||||
self.schemas.insert(id, schema_arc);
|
||||
}
|
||||
|
||||
self.collect_schemas(errors);
|
||||
|
||||
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
|
||||
for (id, schema_arc) in &self.schemas {
|
||||
// First compile pass initializes exact structural root_id mapping to resolve DB constraints
|
||||
let root_id = id.split('/').next().unwrap_or(id);
|
||||
schema_arc
|
||||
.as_ref()
|
||||
.compile(self, root_id, id.clone(), errors);
|
||||
// Formally evaluate properties with strict 3-pass Ordered Graph execution natively
|
||||
for (_, enum_def) in &self.enums {
|
||||
for (schema_id, schema_arc) in &enum_def.schemas {
|
||||
let root_id = schema_id.split('/').next().unwrap_or(schema_id);
|
||||
schema_arc.as_ref().compile(self, root_id, schema_id.clone(), errors);
|
||||
}
|
||||
}
|
||||
for (_, type_def) in &self.types {
|
||||
for (schema_id, schema_arc) in &type_def.schemas {
|
||||
let root_id = schema_id.split('/').next().unwrap_or(schema_id);
|
||||
schema_arc.as_ref().compile(self, root_id, schema_id.clone(), errors);
|
||||
}
|
||||
}
|
||||
for (_, punc_def) in &self.puncs {
|
||||
for (schema_id, schema_arc) in &punc_def.schemas {
|
||||
let root_id = schema_id.split('/').next().unwrap_or(schema_id);
|
||||
schema_arc.as_ref().compile(self, root_id, schema_id.clone(), errors);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Synthesize Composed Filter References
|
||||
@ -260,16 +233,15 @@ impl Database {
|
||||
|
||||
let mut filter_ids = Vec::new();
|
||||
for (type_name, id, filter_arc) in filter_schemas {
|
||||
filter_ids.push(id.clone());
|
||||
self.schemas.insert(id.clone(), filter_arc.clone());
|
||||
filter_ids.push((type_name.clone(), id.clone()));
|
||||
if let Some(t) = self.types.get_mut(&type_name) {
|
||||
t.schemas.insert(id, filter_arc);
|
||||
}
|
||||
}
|
||||
|
||||
// Now actively compile the newly injected filters to lock all nested compose references natively
|
||||
for id in filter_ids {
|
||||
if let Some(filter_arc) = self.schemas.get(&id).cloned() {
|
||||
for (type_name, id) in filter_ids {
|
||||
if let Some(filter_arc) = self.types.get(&type_name).and_then(|t| t.schemas.get(&id)).cloned() {
|
||||
let root_id = id.split('/').next().unwrap_or(&id);
|
||||
filter_arc
|
||||
.as_ref()
|
||||
@ -282,13 +254,11 @@ impl Database {
|
||||
let mut type_insert = Vec::new();
|
||||
let mut punc_insert = Vec::new();
|
||||
let mut enum_insert = Vec::new();
|
||||
let mut global_insert = Vec::new();
|
||||
|
||||
// Pass 1: Extract all Schemas structurally off top level definitions into the master registry.
|
||||
// Validate every node recursively via string filters natively!
|
||||
for (type_name, type_def) in &self.types {
|
||||
for (id, schema_arc) in &type_def.schemas {
|
||||
global_insert.push((id.clone(), Arc::clone(schema_arc)));
|
||||
let mut local_insert = Vec::new();
|
||||
crate::database::schema::Schema::collect_schemas(
|
||||
schema_arc,
|
||||
@ -299,14 +269,12 @@ impl Database {
|
||||
);
|
||||
for entry in &local_insert {
|
||||
type_insert.push((type_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
|
||||
global_insert.push((entry.0.clone(), Arc::clone(&entry.1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (punc_name, punc_def) in &self.puncs {
|
||||
for (id, schema_arc) in &punc_def.schemas {
|
||||
global_insert.push((id.clone(), Arc::clone(schema_arc)));
|
||||
let mut local_insert = Vec::new();
|
||||
crate::database::schema::Schema::collect_schemas(
|
||||
schema_arc,
|
||||
@ -317,14 +285,12 @@ impl Database {
|
||||
);
|
||||
for entry in &local_insert {
|
||||
punc_insert.push((punc_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
|
||||
global_insert.push((entry.0.clone(), Arc::clone(&entry.1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (enum_name, enum_def) in &self.enums {
|
||||
for (id, schema_arc) in &enum_def.schemas {
|
||||
global_insert.push((id.clone(), Arc::clone(schema_arc)));
|
||||
let mut local_insert = Vec::new();
|
||||
crate::database::schema::Schema::collect_schemas(
|
||||
schema_arc,
|
||||
@ -335,16 +301,10 @@ impl Database {
|
||||
);
|
||||
for entry in &local_insert {
|
||||
enum_insert.push((enum_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
|
||||
global_insert.push((entry.0.clone(), Arc::clone(&entry.1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply global inserts
|
||||
for (id, schema_arc) in global_insert {
|
||||
self.schemas.insert(id, schema_arc);
|
||||
}
|
||||
|
||||
// Apply local scopes
|
||||
for (origin_name, id, schema_arc) in type_insert {
|
||||
if let Some(t) = self.types.get_mut(&origin_name) {
|
||||
@ -362,6 +322,40 @@ impl Database {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_scoped_schema(&self, realm: SchemaRealm, schema_id: &str) -> Option<Arc<Schema>> {
|
||||
// Punc Realm natively maps mathematically to `.request` and `.response` shapes
|
||||
if realm == SchemaRealm::Punc {
|
||||
if schema_id.ends_with(".request") || schema_id.ends_with(".response") {
|
||||
let punc_name = schema_id
|
||||
.trim_end_matches(".request")
|
||||
.trim_end_matches(".response");
|
||||
return self.puncs.get(punc_name).and_then(|p| p.schemas.get(schema_id).cloned());
|
||||
}
|
||||
}
|
||||
|
||||
let clean_id = schema_id.trim_end_matches(".filter");
|
||||
let root_id = clean_id.split('/').next().unwrap_or(clean_id);
|
||||
let base_name = root_id.split('.').next_back().unwrap_or(root_id);
|
||||
|
||||
// Puncs and Types can lookup Table boundaries
|
||||
if realm == SchemaRealm::Type || realm == SchemaRealm::Punc {
|
||||
if let Some(type_def) = self.types.get(base_name) {
|
||||
if let Some(schema) = type_def.schemas.get(schema_id) {
|
||||
return Some(schema.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All realms can intrinsically look up enumerations
|
||||
if let Some(enum_def) = self.enums.get(base_name) {
|
||||
if let Some(schema) = enum_def.schemas.get(schema_id) {
|
||||
return Some(schema.clone());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Inspects the Postgres pg_constraint relations catalog to securely identify
|
||||
/// the precise Foreign Key connecting a parent and child hierarchy path.
|
||||
|
||||
6
src/database/realm.rs
Normal file
6
src/database/realm.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SchemaRealm {
|
||||
Enum,
|
||||
Type,
|
||||
Punc,
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
pub mod cache;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::database::realm::SchemaRealm;
|
||||
use crate::database::r#type::Type;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
@ -24,8 +25,8 @@ impl Merger {
|
||||
pub fn merge(&self, schema_id: &str, data: Value) -> crate::drop::Drop {
|
||||
let mut notifications_queue = Vec::new();
|
||||
|
||||
let target_schema = match self.db.schemas.get(schema_id) {
|
||||
Some(s) => Arc::clone(s),
|
||||
let target_schema = match self.db.get_scoped_schema(SchemaRealm::Type, schema_id) {
|
||||
Some(s) => Arc::clone(&s),
|
||||
None => {
|
||||
return crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: "MERGE_FAILED".to_string(),
|
||||
@ -144,30 +145,49 @@ impl Merger {
|
||||
if let Some(v) = val {
|
||||
if let Some((idx_opt, target_id_opt)) = options.get(v) {
|
||||
if let Some(target_id) = target_id_opt {
|
||||
if let Some(target_schema) = self.db.schemas.get(target_id) {
|
||||
schema = Arc::clone(target_schema);
|
||||
if let Some(target_schema) =
|
||||
self.db.get_scoped_schema(SchemaRealm::Type, target_id)
|
||||
{
|
||||
schema = target_schema.clone();
|
||||
} else {
|
||||
return Err(format!("Polymorphic mapped target '{}' not found in database registry", target_id));
|
||||
return Err(format!(
|
||||
"Polymorphic mapped target '{}' not found in database registry",
|
||||
target_id
|
||||
));
|
||||
}
|
||||
} else if let Some(idx) = idx_opt {
|
||||
if let Some(target_schema) = schema.obj.one_of.as_ref().and_then(|options| options.get(*idx)) {
|
||||
schema = Arc::clone(target_schema);
|
||||
if let Some(target_schema) = schema
|
||||
.obj
|
||||
.one_of
|
||||
.as_ref()
|
||||
.and_then(|options| options.get(*idx))
|
||||
{
|
||||
schema = Arc::clone(target_schema);
|
||||
} else {
|
||||
return Err(format!("Polymorphic index target '{}' not found in local oneOf array", idx));
|
||||
return Err(format!(
|
||||
"Polymorphic index target '{}' not found in local oneOf array",
|
||||
idx
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Polymorphic mapped target has no path"));
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Polymorphic discriminator {}='{}' matched no compiled options", disc, v));
|
||||
return Err(format!(
|
||||
"Polymorphic discriminator {}='{}' matched no compiled options",
|
||||
disc, v
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Polymorphic merging failed: missing required discriminator '{}'", disc));
|
||||
return Err(format!(
|
||||
"Polymorphic merging failed: missing required discriminator '{}'",
|
||||
disc
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.merge_object(schema, map, notifications)
|
||||
},
|
||||
}
|
||||
_ => Err("Invalid merge payload: root must be an Object or Array".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use crate::database::Database;
|
||||
use crate::database::realm::SchemaRealm;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Compiler<'a> {
|
||||
@ -24,13 +25,18 @@ pub struct Node<'a> {
|
||||
impl<'a> Compiler<'a> {
|
||||
/// Compiles a JSON schema into a nested PostgreSQL query returning JSONB
|
||||
pub fn compile(&self, schema_id: &str, filter_keys: &[String]) -> Result<String, String> {
|
||||
let realm = if schema_id.ends_with(".request") || schema_id.ends_with(".response") {
|
||||
SchemaRealm::Punc
|
||||
} else {
|
||||
SchemaRealm::Type
|
||||
};
|
||||
|
||||
let schema = self
|
||||
.db
|
||||
.schemas
|
||||
.get(schema_id)
|
||||
.get_scoped_schema(realm, schema_id)
|
||||
.ok_or_else(|| format!("Schema not found: {}", schema_id))?;
|
||||
|
||||
let target_schema = std::sync::Arc::clone(schema);
|
||||
let target_schema = schema;
|
||||
|
||||
let mut compiler = Compiler {
|
||||
db: &self.db,
|
||||
@ -151,9 +157,9 @@ impl<'a> Compiler<'a> {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &node.schema.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
// If it's just an ad-hoc struct ref, we should resolve it
|
||||
if let Some(target_schema) = self.db.schemas.get(t) {
|
||||
if let Some(target_schema) = self.db.get_scoped_schema(SchemaRealm::Type, t) {
|
||||
let mut ref_node = node.clone();
|
||||
ref_node.schema = Arc::clone(target_schema);
|
||||
ref_node.schema = target_schema.clone();
|
||||
ref_node.schema_id = Some(t.clone());
|
||||
return self.compile_node(ref_node);
|
||||
}
|
||||
@ -306,9 +312,9 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
for (disc_val, (idx_opt, target_id_opt)) in options {
|
||||
if let Some(target_id) = target_id_opt {
|
||||
if let Some(target_schema) = self.db.schemas.get(target_id) {
|
||||
if let Some(target_schema) = self.db.get_scoped_schema(SchemaRealm::Type, target_id) {
|
||||
let mut child_node = node.clone();
|
||||
child_node.schema = Arc::clone(target_schema);
|
||||
child_node.schema = target_schema.clone();
|
||||
child_node.schema_id = Some(target_id.clone());
|
||||
child_node.is_polymorphic_branch = true;
|
||||
|
||||
|
||||
@ -64,12 +64,6 @@ impl Case {
|
||||
let validator = Validator::new(db);
|
||||
|
||||
let schema_id = &self.schema_id;
|
||||
if !validator.db.schemas.contains_key(schema_id) {
|
||||
return Err(format!(
|
||||
"Missing Schema: Cannot find schema ID '{}'",
|
||||
schema_id
|
||||
));
|
||||
}
|
||||
|
||||
let test_data = self.data.clone().unwrap_or(Value::Null);
|
||||
let result = validator.validate(schema_id, &test_data);
|
||||
|
||||
@ -5,7 +5,16 @@ impl Expect {
|
||||
pub fn assert_schemas(&self, db: &Arc<crate::database::Database>) -> Result<(), String> {
|
||||
if let Some(expected_map) = &self.schemas {
|
||||
// Collect actual schemas and sort
|
||||
let mut actual: Vec<String> = db.schemas.keys().cloned().collect();
|
||||
let mut actual: Vec<String> = Vec::new();
|
||||
for type_def in db.types.values() {
|
||||
actual.extend(type_def.schemas.keys().cloned());
|
||||
}
|
||||
for punc_def in db.puncs.values() {
|
||||
actual.extend(punc_def.schemas.keys().cloned());
|
||||
}
|
||||
for enum_def in db.enums.values() {
|
||||
actual.extend(enum_def.schemas.keys().cloned());
|
||||
}
|
||||
actual.sort();
|
||||
|
||||
// Collect expected schemas and sort
|
||||
@ -26,7 +35,12 @@ impl Expect {
|
||||
if expected_val.is_object() && expected_val.as_object().unwrap().is_empty() {
|
||||
continue; // A `{}` means we just wanted to test it was collected/promoted, skip deep match
|
||||
}
|
||||
let actual_ast = db.schemas.get(key).unwrap();
|
||||
let schema_realm = if key.ends_with(".request") || key.ends_with(".response") {
|
||||
crate::database::realm::SchemaRealm::Punc
|
||||
} else {
|
||||
crate::database::realm::SchemaRealm::Type
|
||||
};
|
||||
let actual_ast = db.get_scoped_schema(schema_realm, key).unwrap();
|
||||
let actual_val = serde_json::to_value(actual_ast).unwrap();
|
||||
|
||||
if actual_val != *expected_val {
|
||||
|
||||
@ -10,6 +10,7 @@ pub use error::ValidationError;
|
||||
pub use result::ValidationResult;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::database::realm::SchemaRealm;
|
||||
use crate::validator::rules::util::is_integer;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
@ -23,10 +24,6 @@ impl Validator {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub fn get_schema_ids(&self) -> Vec<String> {
|
||||
self.db.schemas.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn check_type(t: &str, val: &Value) -> bool {
|
||||
if let Value::String(s) = val
|
||||
&& s.is_empty()
|
||||
@ -46,11 +43,17 @@ impl Validator {
|
||||
}
|
||||
|
||||
pub fn validate(&self, schema_id: &str, instance: &Value) -> crate::drop::Drop {
|
||||
if let Some(schema) = self.db.schemas.get(schema_id) {
|
||||
let schema_opt = if schema_id.ends_with(".request") || schema_id.ends_with(".response") {
|
||||
self.db.get_scoped_schema(SchemaRealm::Punc, schema_id)
|
||||
} else {
|
||||
self.db.get_scoped_schema(SchemaRealm::Type, schema_id)
|
||||
};
|
||||
|
||||
if let Some(schema) = schema_opt {
|
||||
let ctx = ValidationContext::new(
|
||||
&self.db,
|
||||
schema,
|
||||
schema,
|
||||
&schema,
|
||||
&schema,
|
||||
instance,
|
||||
HashSet::new(),
|
||||
false,
|
||||
|
||||
@ -23,10 +23,13 @@ impl<'a> ValidationContext<'a> {
|
||||
// Entity implicit type validation
|
||||
if let Some(ref schema_identifier_str) = schema_identifier {
|
||||
// We decompose identity string routing inherently
|
||||
let expected_type = schema_identifier_str.split('.').last().unwrap_or(schema_identifier_str);
|
||||
|
||||
let expected_type = schema_identifier_str
|
||||
.split('.')
|
||||
.last()
|
||||
.unwrap_or(schema_identifier_str);
|
||||
|
||||
// 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_def) = self.db.types.get(expected_type).filter(|t| !t.variations.is_empty()) {
|
||||
if let Some(type_val) = obj.get("type") {
|
||||
if let Some(type_str) = type_val.as_str() {
|
||||
if type_def.variations.contains(type_str) {
|
||||
@ -47,21 +50,28 @@ impl<'a> ValidationContext<'a> {
|
||||
// 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),
|
||||
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_str.contains('.') {
|
||||
let requires_kind = self.schema.compiled_properties.get()
|
||||
.map_or(false, |p| p.contains_key("kind"));
|
||||
|
||||
let requires_kind = self
|
||||
.schema
|
||||
.compiled_properties
|
||||
.get()
|
||||
.map_or(false, |p| p.contains_key("kind"));
|
||||
|
||||
if requires_kind {
|
||||
if obj.get("kind").is_none() {
|
||||
result.errors.push(ValidationError {
|
||||
code: "MISSING_KIND".to_string(),
|
||||
message: "Schema mechanically requires horizontal kind discrimination".to_string(),
|
||||
message: "Schema mechanically requires horizontal kind discrimination"
|
||||
.to_string(),
|
||||
path: self.path.clone(),
|
||||
});
|
||||
} else {
|
||||
@ -74,20 +84,20 @@ impl<'a> ValidationContext<'a> {
|
||||
// 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(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_str.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((kind_str, _)) = schema_identifier_str.rsplit_once('.') {
|
||||
if let Some(actual_kind) = kind_val.as_str() {
|
||||
if actual_kind == kind_str {
|
||||
result.evaluated_keys.insert("kind".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,7 +177,9 @@ impl<'a> ValidationContext<'a> {
|
||||
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::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
|
||||
!crate::database::object::is_primitive_type(t)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let next_extensible = if is_ref { false } else { self.extensible };
|
||||
@ -182,8 +194,6 @@ impl<'a> ValidationContext<'a> {
|
||||
);
|
||||
let item_res = derived.validate()?;
|
||||
|
||||
|
||||
|
||||
result.merge(item_res);
|
||||
result.evaluated_keys.insert(key.to_string());
|
||||
}
|
||||
@ -196,7 +206,9 @@ impl<'a> ValidationContext<'a> {
|
||||
if compiled_re.0.is_match(key) {
|
||||
let new_path = self.join_path(key);
|
||||
let is_ref = match &sub_schema.type_ {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
|
||||
!crate::database::object::is_primitive_type(t)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let next_extensible = if is_ref { false } else { self.extensible };
|
||||
@ -225,7 +237,8 @@ impl<'a> ValidationContext<'a> {
|
||||
{
|
||||
locally_matched = true;
|
||||
}
|
||||
if !locally_matched && let Some(compiled_pp) = self.schema.compiled_pattern_properties.get()
|
||||
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) {
|
||||
@ -238,7 +251,9 @@ impl<'a> ValidationContext<'a> {
|
||||
if !locally_matched {
|
||||
let new_path = self.join_path(key);
|
||||
let is_ref = match &additional_schema.type_ {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
|
||||
!crate::database::object::is_primitive_type(t)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let next_extensible = if is_ref { false } else { self.extensible };
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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(
|
||||
@ -99,8 +100,8 @@ impl<'a> ValidationContext<'a> {
|
||||
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.as_ref(), false);
|
||||
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);
|
||||
@ -221,21 +222,21 @@ impl<'a> ValidationContext<'a> {
|
||||
}
|
||||
|
||||
for t in custom_types {
|
||||
if let Some(global_schema) = self.db.schemas.get(&t) {
|
||||
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,
|
||||
&global_schema,
|
||||
self.instance,
|
||||
&self.path,
|
||||
new_overrides,
|
||||
self.extensible,
|
||||
true, // Reporter mode
|
||||
);
|
||||
shadow.root = global_schema;
|
||||
shadow.root = &global_schema;
|
||||
result.merge(shadow.validate()?);
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
|
||||
Reference in New Issue
Block a user