networknt / json-schema-validator

A fast Java JSON schema validator that supports draft V4, V6, V7, V2019-09 and V2020-12
Apache License 2.0
859 stars 325 forks source link

`JsonSchema.getSubSchema()` fails with JsonPointer containing a number #1076

Closed Mahoney closed 4 months ago

Mahoney commented 4 months ago

The following test fails:

    /**
     * Test can validate against a schema located under a response code.
     */
    @Test
    void canValidateSchemaUnderAResponse() {
        JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
                builder -> builder.metaSchema(OpenApi31.getInstance())
                        .metaSchemaFactory(DisallowUnknownJsonMetaSchemaFactory.getInstance()));
        JsonSchema schema = factory
                .getSchema(SchemaLocation.of("classpath:schema/oas/3.0/petstore.yaml#/paths/~1pet/post/responses/200/content/application~1json/schema"));
        String input = "{\r\n"
                + "  \"petType\": \"dog\""
                + "}";
        Set<ValidationMessage> messages = schema.validate(input, InputFormat.JSON);
        assertEquals(4, messages.size());
    }

Stacktrace:

com.networknt.schema.InvalidSchemaRefException: /paths/~1pet/post/responses: Reference /paths/~1pet/post/responses/200/content/application~1json/schema cannot be resolved

    at com.networknt.schema.JsonSchema.getSubSchema(JsonSchema.java:415)
    at com.networknt.schema.JsonSchema.getRefSchema(JsonSchema.java:330)
    at com.networknt.schema.JsonSchemaFactory.getMappedSchema(JsonSchemaFactory.java:693)
    at com.networknt.schema.JsonSchemaFactory.loadSchema(JsonSchemaFactory.java:639)
    at com.networknt.schema.JsonSchemaFactory.getSchema(JsonSchemaFactory.java:617)
    at com.networknt.schema.JsonSchemaFactory.getSchema(JsonSchemaFactory.java:743)
    at com.networknt.schema.oas.OpenApi30Test.canValidateSchemaUnderAResponse(OpenApi30Test.java:77)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

The problem is that JsonNodePath.getElement(int index) is returning an Integer for 200, and treating it as an array index, when it should also treat it as a valid String key name to look up a value on an object.

Mahoney commented 4 months ago

Patch fix:

Subject: [PATCH] Fix for #1076
---
Index: src/main/java/com/networknt/schema/utils/JsonNodes.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/com/networknt/schema/utils/JsonNodes.java b/src/main/java/com/networknt/schema/utils/JsonNodes.java
--- a/src/main/java/com/networknt/schema/utils/JsonNodes.java   (revision 46da4db03c362d472521132f8bd225bc41ef7b41)
+++ b/src/main/java/com/networknt/schema/utils/JsonNodes.java   (date 1719833086422)
@@ -24,6 +24,7 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.networknt.schema.JsonNodePath;
 import com.networknt.schema.serialization.node.JsonLocationAware;
@@ -67,7 +68,7 @@
     @SuppressWarnings("unchecked")
     public static <T extends JsonNode> T get(JsonNode node, Object propertyOrIndex) {
         JsonNode value = null;
-        if (propertyOrIndex instanceof Number) {
+        if (node instanceof ArrayNode && propertyOrIndex instanceof Number) {
             value = node.get(((Number) propertyOrIndex).intValue());
         } else {
             value = node.get(propertyOrIndex.toString());
Index: src/test/java/com/networknt/schema/oas/OpenApi30Test.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/java/com/networknt/schema/oas/OpenApi30Test.java b/src/test/java/com/networknt/schema/oas/OpenApi30Test.java
--- a/src/test/java/com/networknt/schema/oas/OpenApi30Test.java (revision 46da4db03c362d472521132f8bd225bc41ef7b41)
+++ b/src/test/java/com/networknt/schema/oas/OpenApi30Test.java (date 1719832799750)
@@ -64,4 +64,21 @@
         assertEquals("required", list.get(1).getType());
         assertEquals("bark", list.get(1).getProperty());
     }
+
+    /**
+     * Test can validate against a schema located under a response code.
+     */
+    @Test
+    void canValidateSchemaUnderAResponse() {
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+                builder -> builder.metaSchema(OpenApi31.getInstance())
+                        .metaSchemaFactory(DisallowUnknownJsonMetaSchemaFactory.getInstance()));
+        JsonSchema schema = factory
+                .getSchema(SchemaLocation.of("classpath:schema/oas/3.0/petstore.yaml#/paths/~1pet/post/responses/200/content/application~1json/schema"));
+        String input = "{\r\n"
+                + "  \"petType\": \"dog\""
+                + "}";
+        Set<ValidationMessage> messages = schema.validate(input, InputFormat.JSON);
+        assertEquals(4, messages.size());
+    }
 }
justin-tay commented 4 months ago

Thanks for the bug report and proposed fix.