From 7677cc47d37b0208774184b9c22d3baea98b2122 Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Sun, 8 Mar 2026 15:24:52 -0400 Subject: [PATCH] oneOf and discriminator value mismatch doesnot fail --- .../schema/keyword/OneOfValidator.java | 17 ++++- .../schema/DiscriminatorValidatorTest.java | 73 ++++++++++++++++++- .../networknt/schema/oas/OpenApi31Test.java | 17 ++++- 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/networknt/schema/keyword/OneOfValidator.java b/src/main/java/com/networknt/schema/keyword/OneOfValidator.java index a93632085..3895d0fbe 100644 --- a/src/main/java/com/networknt/schema/keyword/OneOfValidator.java +++ b/src/main/java/com/networknt/schema/keyword/OneOfValidator.java @@ -168,7 +168,7 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo * schema can be determined and validation SHOULD fail. Mapping keys MUST be * string values, but tooling MAY convert response values to strings for * comparison. - * + * * https://spec.openapis.org/oas/v3.1.2#examples-0 */ DiscriminatorState state = executionContext.getDiscriminatorMapping().get(instanceLocation); @@ -183,7 +183,22 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo .messageKey("discriminator.oneOf.no_match_found") .arguments(state.getDiscriminatingValue()).build()); } + /* + * Issue 1225: When the discriminator-indicated schema failed (discriminatorErrors + * is set) but another non-discriminated schema happened to pass (numberOfValidSchema == 1), + * validation incorrectly succeeds. The discriminator mapping constitutes an assertion + * that the payload should validate against the mapped schema. If the mapped schema + * fails but a different schema passes, this is a discriminator mismatch and should fail. + */ + if (discriminatorErrors != null && numberOfValidSchema == 1 && state != null && state.hasDiscriminatingValue()) { + existingErrors + .add(error().keyword("discriminator").instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) + .messageKey("discriminator.oneOf.no_match_found") + .arguments(state.getDiscriminatingValue()).build()); + } } + } finally { // Restore flag executionContext.setFailFast(failFast); diff --git a/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java b/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java index 1c4857c08..f8eb97194 100644 --- a/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java +++ b/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java @@ -980,5 +980,76 @@ void anyOfRedefinedDiscriminatorAndDiscriminatorWithMissingPropertyName() { // There is still a schema in the anyOf that matches assertTrue(list.isEmpty()); } - + + /** + * Issue 1225. + *

+ * When oneOf with discriminator mapping is used and the discriminating value + * maps to a specific schema (e.g., type=string -> $defs/string), but the + * data only validates against a different schema (e.g., $defs/number), + * validation should fail because the discriminator-indicated schema is not + * the one that matches. + */ + @Test + void oneOfDiscriminatorEnabledWithDiscriminatorMismatch() { + String schemaData = "{\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"type\",\r\n" + + " \"mapping\": {\r\n" + + " \"string\": \"#/$defs/string\",\r\n" + + " \"number\": \"#/$defs/number\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/string\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/number\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"$defs\": {\r\n" + + " \"string\": {\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"number\": {\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); + Schema schema = factory.getSchema(schemaData); + + // type=string maps to $defs/string via explicit discriminator mapping. + // However, value=1 is a number, so $defs/string fails (value must be string). + // $defs/number succeeds (value=1 is a valid number). + // oneOf passes because exactly one schema matches, but it's the WRONG schema. + // The discriminator says type=string should map to $defs/string, so this should fail. + String inputData = "{\r\n" + + " \"type\": \"string\",\r\n" + + " \"value\": 1\r\n" + + "}"; + List messages = schema.validate(inputData, InputFormat.JSON); + // This should be invalid because type=string maps to $defs/string via discriminator, + // but the data does NOT validate against $defs/string (value:1 is not a string). + // The discriminator mapping mismatch means the wrong schema matched. + assertEquals(1, messages.size()); + assertEquals("discriminator", messages.get(0).getKeyword()); + } + } diff --git a/src/test/java/com/networknt/schema/oas/OpenApi31Test.java b/src/test/java/com/networknt/schema/oas/OpenApi31Test.java index 84ab471b2..ae5d9ec21 100644 --- a/src/test/java/com/networknt/schema/oas/OpenApi31Test.java +++ b/src/test/java/com/networknt/schema/oas/OpenApi31Test.java @@ -129,11 +129,17 @@ void discriminatorOneOfNoMatchShouldFail() { } /** - * Test oneOf with one match but incorrect discriminator should succeed. Note - * that the discriminator does not affect the validation outcome. + * Test oneOf with one match but incorrect discriminator should fail. + *

+ * When petType=dog maps to the Dog schema via discriminator mapping, but the + * data only validates against Lizard (because lovesRocks=true satisfies Lizard), + * validation should fail because the discriminator-indicated schema (Dog) is not + * the one that matched. + *

+ * Fix for issue 1225. */ @Test - void discriminatorOneOfOneMatchWrongDiscriminatorShouldSucceed() { + void discriminatorOneOfOneMatchWrongDiscriminatorShouldFail() { SchemaRegistry factory = SchemaRegistry.withDialect(Dialects.getOpenApi31()); Schema schema = factory .getSchema(SchemaLocation.of("classpath:schema/oas/3.1/petstore.yaml#/components/schemas/PetResponse")); @@ -142,7 +148,10 @@ void discriminatorOneOfOneMatchWrongDiscriminatorShouldSucceed() { + " \"lovesRocks\": true\r\n" + "}"; List messages = schema.validate(input, InputFormat.JSON); - assertEquals(0, messages.size()); + // petType=dog maps Dog schema via discriminator, but data only validates against Lizard. + // The discriminator-indicated schema (Dog) is not the one that matched, so this should fail. + assertEquals(1, messages.size()); + assertEquals("discriminator", messages.get(0).getKeyword()); } }