From c4e8e0309fe4a904d8a70e7ee96699f5c5b50a4f Mon Sep 17 00:00:00 2001 From: Alex Groleau Date: Thu, 26 Mar 2026 22:27:59 -0400 Subject: [PATCH] removed initial / in validator making paths consistent across validate merger and queryer --- fixtures/emptyString.json | 2 +- fixtures/merge.json | 10 ++--- fixtures/paths.json | 68 ++++++++++++++++++++++------- fixtures/ref.json | 4 +- src/tests/mod.rs | 4 +- src/validator/context.rs | 8 ++++ src/validator/rules/array.rs | 8 ++-- src/validator/rules/conditionals.rs | 6 +-- src/validator/rules/object.rs | 12 ++--- 9 files changed, 83 insertions(+), 39 deletions(-) diff --git a/fixtures/emptyString.json b/fixtures/emptyString.json index 765089d..b7b0a07 100644 --- a/fixtures/emptyString.json +++ b/fixtures/emptyString.json @@ -142,7 +142,7 @@ "errors": [ { "code": "CONST_VIOLATED", - "path": "/con" + "path": "con" } ] } diff --git a/fixtures/merge.json b/fixtures/merge.json index 7281f6d..c9d91d2 100644 --- a/fixtures/merge.json +++ b/fixtures/merge.json @@ -48,7 +48,7 @@ "errors": [ { "code": "TYPE_MISMATCH", - "path": "/base_prop" + "path": "base_prop" } ] } @@ -109,7 +109,7 @@ "errors": [ { "code": "REQUIRED_FIELD_MISSING", - "path": "/a" + "path": "a" } ] } @@ -126,7 +126,7 @@ "errors": [ { "code": "REQUIRED_FIELD_MISSING", - "path": "/b" + "path": "b" } ] } @@ -196,7 +196,7 @@ "errors": [ { "code": "DEPENDENCY_FAILED", - "path": "/base_dep" + "path": "base_dep" } ] } @@ -214,7 +214,7 @@ "errors": [ { "code": "DEPENDENCY_FAILED", - "path": "/child_dep" + "path": "child_dep" } ] } diff --git a/fixtures/paths.json b/fixtures/paths.json index 416e472..f4ac0b6 100644 --- a/fixtures/paths.json +++ b/fixtures/paths.json @@ -75,13 +75,30 @@ { "description": "happy path passes structural validation", "data": { - "primitives": ["a", "b"], - "ad_hoc_objects": [{"name": "obj1"}], - "entities": [{"id": "entity-1", "value": 15}], + "primitives": [ + "a", + "b" + ], + "ad_hoc_objects": [ + { + "name": "obj1" + } + ], + "entities": [ + { + "id": "entity-1", + "value": 15 + } + ], "deep_entities": [ { "id": "parent-1", - "nested": [{"id": "child-1", "flag": true}] + "nested": [ + { + "id": "child-1", + "flag": true + } + ] } ] }, @@ -94,7 +111,10 @@ { "description": "primitive arrays use numeric indexing", "data": { - "primitives": ["a", 123] + "primitives": [ + "a", + 123 + ] }, "schema_id": "hybrid_pathing", "action": "validate", @@ -103,7 +123,7 @@ "errors": [ { "code": "INVALID_TYPE", - "path": "/primitives/1" + "path": "primitives/1" } ] } @@ -112,8 +132,12 @@ "description": "ad-hoc objects without ids use numeric indexing", "data": { "ad_hoc_objects": [ - {"name": "valid"}, - {"age": 30} + { + "name": "valid" + }, + { + "age": 30 + } ] }, "schema_id": "hybrid_pathing", @@ -123,7 +147,7 @@ "errors": [ { "code": "REQUIRED_FIELD_MISSING", - "path": "/ad_hoc_objects/1/name" + "path": "ad_hoc_objects/1/name" } ] } @@ -132,8 +156,14 @@ "description": "arrays of objects with ids use topological uuid indexing", "data": { "entities": [ - {"id": "entity-alpha", "value": 20}, - {"id": "entity-beta", "value": 5} + { + "id": "entity-alpha", + "value": 20 + }, + { + "id": "entity-beta", + "value": 5 + } ] }, "schema_id": "hybrid_pathing", @@ -143,7 +173,7 @@ "errors": [ { "code": "MINIMUM_VIOLATED", - "path": "/entities/entity-beta/value" + "path": "entities/entity-beta/value" } ] } @@ -155,8 +185,14 @@ { "id": "parent-omega", "nested": [ - {"id": "child-alpha", "flag": true}, - {"id": "child-beta", "flag": "invalid-string"} + { + "id": "child-alpha", + "flag": true + }, + { + "id": "child-beta", + "flag": "invalid-string" + } ] } ] @@ -168,11 +204,11 @@ "errors": [ { "code": "INVALID_TYPE", - "path": "/deep_entities/parent-omega/nested/child-beta/flag" + "path": "deep_entities/parent-omega/nested/child-beta/flag" } ] } } ] } -] +] \ No newline at end of file diff --git a/fixtures/ref.json b/fixtures/ref.json index 1010761..6eb9beb 100644 --- a/fixtures/ref.json +++ b/fixtures/ref.json @@ -677,7 +677,7 @@ "errors": [ { "code": "TYPE_MISMATCH", - "path": "/type" + "path": "type" } ] } @@ -782,7 +782,7 @@ "errors": [ { "code": "TYPE_MISMATCH", - "path": "/type" + "path": "type" } ] } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index bfff180..3e30572 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -134,12 +134,12 @@ fn test_library_api() { { "code": "REQUIRED_FIELD_MISSING", "message": "Missing name", - "details": { "path": "/name" } + "details": { "path": "name" } }, { "code": "STRICT_PROPERTY_VIOLATION", "message": "Unexpected property 'wrong'", - "details": { "path": "/wrong" } + "details": { "path": "wrong" } } ] }) diff --git a/src/validator/context.rs b/src/validator/context.rs index 6850488..0efe036 100644 --- a/src/validator/context.rs +++ b/src/validator/context.rs @@ -41,6 +41,14 @@ impl<'a> ValidationContext<'a> { } } + pub fn join_path(&self, key: &str) -> String { + if self.path.is_empty() { + key.to_string() + } else { + format!("{}/{}", self.path, key) + } + } + pub fn derive( &self, schema: &'a Schema, diff --git a/src/validator/rules/array.rs b/src/validator/rules/array.rs index 56acbfb..b374c51 100644 --- a/src/validator/rules/array.rs +++ b/src/validator/rules/array.rs @@ -92,10 +92,10 @@ impl<'a> ValidationContext<'a> { for (i, sub_schema) in prefix.iter().enumerate() { if i < len { if let Some(child_instance) = arr.get(i) { - let mut item_path = format!("{}/{}", self.path, i); + let mut item_path = self.join_path(&i.to_string()); if let Some(obj) = child_instance.as_object() { if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) { - item_path = format!("{}/{}", self.path, id_str); + item_path = self.join_path(id_str); } } let derived = self.derive( @@ -118,10 +118,10 @@ impl<'a> ValidationContext<'a> { if let Some(ref items_schema) = self.schema.items { for i in validation_index..len { if let Some(child_instance) = arr.get(i) { - let mut item_path = format!("{}/{}", self.path, i); + let mut item_path = self.join_path(&i.to_string()); if let Some(obj) = child_instance.as_object() { if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) { - item_path = format!("{}/{}", self.path, id_str); + item_path = self.join_path(id_str); } } let derived = self.derive( diff --git a/src/validator/rules/conditionals.rs b/src/validator/rules/conditionals.rs index 076f7c2..9a445e4 100644 --- a/src/validator/rules/conditionals.rs +++ b/src/validator/rules/conditionals.rs @@ -44,7 +44,7 @@ impl<'a> ValidationContext<'a> { result.errors.push(ValidationError { code: "STRICT_PROPERTY_VIOLATION".to_string(), message: format!("Unexpected property '{}'", key), - path: format!("{}/{}", self.path, key), + path: self.join_path(key), }); } } @@ -53,11 +53,11 @@ impl<'a> ValidationContext<'a> { if let Some(arr) = self.instance.as_array() { for i in 0..arr.len() { if !result.evaluated_indices.contains(&i) { - let mut item_path = format!("{}/{}", self.path, i); + let mut item_path = self.join_path(&i.to_string()); if let Some(child_instance) = arr.get(i) { if let Some(obj) = child_instance.as_object() { if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) { - item_path = format!("{}/{}", self.path, id_str); + item_path = self.join_path(id_str); } } } diff --git a/src/validator/rules/object.rs b/src/validator/rules/object.rs index 60ddb3c..423c356 100644 --- a/src/validator/rules/object.rs +++ b/src/validator/rules/object.rs @@ -32,7 +32,7 @@ impl<'a> ValidationContext<'a> { "Type '{}' is not a valid descendant for this entity bound schema", type_str ), - path: format!("{}/type", self.path), + path: self.join_path("type"), }); } } else { @@ -70,7 +70,7 @@ impl<'a> ValidationContext<'a> { result.errors.push(ValidationError { code: "REQUIRED_FIELD_MISSING".to_string(), message: format!("Missing {}", field), - path: format!("{}/{}", self.path, field), + path: self.join_path(field), }); } } @@ -109,7 +109,7 @@ impl<'a> ValidationContext<'a> { } if let Some(child_instance) = obj.get(key) { - let new_path = format!("{}/{}", self.path, key); + let new_path = self.join_path(key); let is_ref = sub_schema.r#ref.is_some(); let next_extensible = if is_ref { false } else { self.extensible }; @@ -147,7 +147,7 @@ impl<'a> ValidationContext<'a> { for (compiled_re, sub_schema) in compiled_pp { for (key, child_instance) in obj { if compiled_re.0.is_match(key) { - let new_path = format!("{}/{}", self.path, key); + let new_path = self.join_path(key); let is_ref = sub_schema.r#ref.is_some(); let next_extensible = if is_ref { false } else { self.extensible }; @@ -186,7 +186,7 @@ impl<'a> ValidationContext<'a> { } if !locally_matched { - let new_path = format!("{}/{}", self.path, key); + let new_path = self.join_path(key); let is_ref = additional_schema.r#ref.is_some(); let next_extensible = if is_ref { false } else { self.extensible }; @@ -207,7 +207,7 @@ impl<'a> ValidationContext<'a> { if let Some(ref property_names) = self.schema.property_names { for key in obj.keys() { - let _new_path = format!("{}/propertyNames/{}", self.path, key); + let _new_path = self.join_path(&format!("propertyNames/{}", key)); let val_str = Value::String(key.to_string()); let ctx = ValidationContext::new(