Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/java/com/networknt/schema/Schema.java
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,8 @@ private JsonNode deserialize(String input, InputFormat inputFormat) {
return this.getSchemaContext().getSchemaRegistry().readTree(input, inputFormat);
} catch (IOException e) {
throw new UncheckedIOException("Invalid input", e);
} catch (RuntimeException e) {
throw new SchemaException("Invalid " + inputFormat + " input: " + e.getMessage(), e);
}
}

Expand All @@ -1213,6 +1215,8 @@ private JsonNode deserialize(AbsoluteIri input, InputFormat inputFormat) {
}
} catch (IOException e) {
throw new UncheckedIOException("Invalid input", e);
} catch (RuntimeException e) {
throw new SchemaException("Invalid " + inputFormat + " input from " + input + ": " + e.getMessage(), e);
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/networknt/schema/SchemaException.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public SchemaException(Throwable throwable) {
this.error = null;
}

public SchemaException(String message, Throwable throwable) {
super(message, throwable);
this.error = null;
}

@Override
public String getMessage() {
return this.error != null ? this.error.getMessage() : super.getMessage();
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/com/networknt/schema/keyword/OneOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -980,5 +980,76 @@ void anyOfRedefinedDiscriminatorAndDiscriminatorWithMissingPropertyName() {
// There is still a schema in the anyOf that matches
assertTrue(list.isEmpty());
}


/**
* Issue 1225.
* <p>
* 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<Error> 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());
}

}
32 changes: 32 additions & 0 deletions src/test/java/com/networknt/schema/SchemaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,36 @@ public void run() {
throw instance[0];
}
}

/**
* Issue 1231.
* <p>
* When the YAML input length lands exactly on the snakeyaml-engine StreamReader
* 1024-char chunk boundary, an IndexOutOfBoundsException is thrown internally.
* This should be caught and wrapped in a SchemaException with a clear message
* rather than propagating as a raw IndexOutOfBoundsException.
*/
@Test
void yamlInputAtChunkBoundaryShouldThrowSchemaException() {
// Build a YAML key that is exactly 1024 chars so the total YAML content
// hits the snakeyaml-engine StreamReader boundary.
String longKey = "a".repeat(1024);
String yamlInput = longKey + ": value\n";

String schemaData = "{\"type\": \"object\"}";
SchemaRegistry factory = SchemaRegistry.withDialect(com.networknt.schema.dialect.Dialects.getOpenApi31());
Schema schema = factory.getSchema(schemaData);

// Before the fix this threw IndexOutOfBoundsException directly.
// After the fix it should throw SchemaException with a clear message.
try {
schema.validate(yamlInput, InputFormat.YAML);
} catch (IndexOutOfBoundsException e) {
// Raw IOOBE must not escape - this is the bug we're fixing
throw new AssertionError("Expected SchemaException but got raw IndexOutOfBoundsException", e);
} catch (RuntimeException e) {
// Any other RuntimeException (including SchemaException) is acceptable
// as long as it's not the raw IndexOutOfBoundsException
}
}
}
17 changes: 13 additions & 4 deletions src/test/java/com/networknt/schema/oas/OpenApi31Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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.
* <p>
* 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"));
Expand All @@ -142,7 +148,10 @@ void discriminatorOneOfOneMatchWrongDiscriminatorShouldSucceed() {
+ " \"lovesRocks\": true\r\n"
+ "}";
List<Error> 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());
}

}