diff --git a/fixtures/polymorphism.json b/fixtures/polymorphism.json index 9017f04..3df37bf 100644 --- a/fixtures/polymorphism.json +++ b/fixtures/polymorphism.json @@ -636,5 +636,110 @@ } } ] + }, + { + "description": "STI Projections (Lacking Kind Discriminator Definitions)", + "database": { + "types": [ + { + "name": "widget", + "variations": [ + "widget" + ], + "schemas": { + "widget": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "stock.widget": { + "type": "widget", + "properties": { + "kind": { + "type": "string" + }, + "amount": { + "type": "integer" + } + } + }, + "projected.widget": { + "type": "widget", + "properties": { + "alias": { + "type": "string" + } + } + } + } + } + ], + "schemas": { + "stock_widget_validation": { + "type": "stock.widget" + }, + "projected_widget_validation": { + "type": "projected.widget" + } + } + }, + "tests": [ + { + "description": "stock.widget securely expects kind when configured", + "schema_id": "stock_widget_validation", + "data": { + "type": "widget", + "amount": 5 + }, + "action": "validate", + "expect": { + "success": false, + "errors": [ + { + "code": "MISSING_KIND", + "details": { + "path": "" + } + } + ] + } + }, + { + "description": "projected.widget seamlessly bypasses kind expectation when excluded from schema", + "schema_id": "projected_widget_validation", + "data": { + "type": "widget", + "alias": "Test Projection" + }, + "action": "validate", + "expect": { + "success": true + } + }, + { + "description": "projected.widget securely fails if user erroneously provides extra kind property", + "schema_id": "projected_widget_validation", + "data": { + "type": "widget", + "alias": "Test Projection", + "kind": "projected" + }, + "action": "validate", + "expect": { + "success": false, + "errors": [ + { + "code": "STRICT_PROPERTY_VIOLATION", + "details": { + "path": "kind" + } + } + ] + } + } + ] } ] \ No newline at end of file diff --git a/src/tests/fixtures.rs b/src/tests/fixtures.rs index 57c65e3..3d06ef4 100644 --- a/src/tests/fixtures.rs +++ b/src/tests/fixtures.rs @@ -1559,6 +1559,24 @@ fn test_polymorphism_4_1() { crate::tests::runner::run_test_case(&path, 4, 1).unwrap(); } +#[test] +fn test_polymorphism_5_0() { + let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR")); + crate::tests::runner::run_test_case(&path, 5, 0).unwrap(); +} + +#[test] +fn test_polymorphism_5_1() { + let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR")); + crate::tests::runner::run_test_case(&path, 5, 1).unwrap(); +} + +#[test] +fn test_polymorphism_5_2() { + let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR")); + crate::tests::runner::run_test_case(&path, 5, 2).unwrap(); +} + #[test] fn test_not_0_0() { let path = format!("{}/fixtures/not.json", env!("CARGO_MANIFEST_DIR")); diff --git a/src/validator/rules/extensible.rs b/src/validator/rules/extensible.rs index bcb0ed3..25cd4dc 100644 --- a/src/validator/rules/extensible.rs +++ b/src/validator/rules/extensible.rs @@ -24,9 +24,6 @@ impl<'a> ValidationContext<'a> { if let Some(obj) = self.instance.as_object() { for key in obj.keys() { - if key == "type" || key == "kind" { - continue; // Reserved keywords implicitly allowed - } if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) { result.errors.push(ValidationError { code: "STRICT_PROPERTY_VIOLATION".to_string(), diff --git a/src/validator/rules/object.rs b/src/validator/rules/object.rs index cd0ec41..2adfba3 100644 --- a/src/validator/rules/object.rs +++ b/src/validator/rules/object.rs @@ -54,14 +54,19 @@ impl<'a> ValidationContext<'a> { // If the target mathematically declares a horizontal structural STI variation natively if schema_identifier_str.contains('.') { - if obj.get("kind").is_none() { - result.errors.push(ValidationError { - code: "MISSING_KIND".to_string(), - message: "Schema mechanically requires horizontal kind discrimination".to_string(), - path: self.path.clone(), - }); - } else { - result.evaluated_keys.insert("kind".to_string()); + 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(), + path: self.path.clone(), + }); + } else { + result.evaluated_keys.insert("kind".to_string()); + } } } } else {