From c71e99527df495ccd6d75ec41c89018422c40ead Mon Sep 17 00:00:00 2001 From: Alex Groleau Date: Wed, 3 Jun 2026 10:50:15 -0400 Subject: [PATCH] dynamic type variables now recursive --- fixtures/dynamicType.json | 49 +++++++++++++++++++++++++++++++++++++ src/tests/fixtures.rs | 12 +++++++++ src/validator/context.rs | 13 +++++++--- src/validator/rules/type.rs | 5 ++-- 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/fixtures/dynamicType.json b/fixtures/dynamicType.json index 0419cab..1cd9f7c 100644 --- a/fixtures/dynamicType.json +++ b/fixtures/dynamicType.json @@ -37,6 +37,14 @@ }, "filter": { "type": "$kind.filter" + }, + "conditions": { + "type": "object", + "properties": { + "new": { "type": "$kind.filter" }, + "old": { "type": "$kind.filter" }, + "complete": { "type": "$kind.filter" } + } } } } @@ -149,7 +157,48 @@ } ] } + }, + { + "description": "Valid nested filter payload", + "data": { + "kind": "person", + "conditions": { + "new": { + "age": 30 + } + } + }, + "schema_id": "search", + "action": "validate", + "expect": { + "success": true + } + }, + { + "description": "Invalid nested filter payload (fails constraint)", + "data": { + "kind": "person", + "conditions": { + "new": { + "age": "thirty" + } + } + }, + "schema_id": "search", + "action": "validate", + "expect": { + "success": false, + "errors": [ + { + "code": "INVALID_TYPE", + "details": { + "path": "conditions/new/age" + } + } + ] + } } ] } ] + diff --git a/src/tests/fixtures.rs b/src/tests/fixtures.rs index 3fe7790..a7c4136 100644 --- a/src/tests/fixtures.rs +++ b/src/tests/fixtures.rs @@ -1277,6 +1277,18 @@ fn test_dynamic_type_0_4() { crate::tests::runner::run_test_case(&path, 0, 4).unwrap(); } +#[test] +fn test_dynamic_type_0_5() { + let path = format!("{}/fixtures/dynamicType.json", env!("CARGO_MANIFEST_DIR")); + crate::tests::runner::run_test_case(&path, 0, 5).unwrap(); +} + +#[test] +fn test_dynamic_type_0_6() { + let path = format!("{}/fixtures/dynamicType.json", env!("CARGO_MANIFEST_DIR")); + crate::tests::runner::run_test_case(&path, 0, 6).unwrap(); +} + #[test] fn test_property_names_0_0() { let path = format!("{}/fixtures/propertyNames.json", env!("CARGO_MANIFEST_DIR")); diff --git a/src/validator/context.rs b/src/validator/context.rs index 6133ccf..94fa39a 100644 --- a/src/validator/context.rs +++ b/src/validator/context.rs @@ -15,7 +15,7 @@ pub struct ValidationContext<'a> { pub extensible: bool, pub reporter: bool, pub overrides: HashSet, - pub parent: Option<&'a serde_json::Value>, + pub parents: Vec<&'a serde_json::Value>, } impl<'a> ValidationContext<'a> { @@ -39,7 +39,7 @@ impl<'a> ValidationContext<'a> { extensible: effective_extensible, reporter, overrides, - parent: None, + parents: Vec::new(), } } @@ -63,6 +63,11 @@ impl<'a> ValidationContext<'a> { ) -> Self { let effective_extensible = schema.extensible.unwrap_or(extensible); + let mut parents = self.parents.clone(); + if let Some(p) = parent_instance { + parents.push(p); + } + Self { db: self.db, root: self.root, @@ -73,7 +78,7 @@ impl<'a> ValidationContext<'a> { extensible: effective_extensible, reporter, overrides, - parent: parent_instance, + parents, } } @@ -85,7 +90,7 @@ impl<'a> ValidationContext<'a> { HashSet::new(), self.extensible, reporter, - self.parent, + None, ) } diff --git a/src/validator/rules/type.rs b/src/validator/rules/type.rs index 37a43f6..c9fb59c 100644 --- a/src/validator/rules/type.rs +++ b/src/validator/rules/type.rs @@ -59,12 +59,13 @@ impl<'a> ValidationContext<'a> { }; let mut resolved = false; - if let Some(parent) = self.parent { + for parent in self.parents.iter().rev() { if let Some(obj) = parent.as_object() { if let Some(val) = obj.get(var_name) { if let Some(str_val) = val.as_str() { target_id = format!("{}{}", str_val, suffix); resolved = true; + break; } } } @@ -97,7 +98,7 @@ impl<'a> ValidationContext<'a> { new_overrides, self.extensible, true, // Reporter mode - self.parent, + None, ); shadow.root = &global_schema; result.merge(shadow.validate()?);