Repository: networknt/json-schema-validator
Pull request #607 (2022-09-07 12:08:37)

Issue 606: Handle matched state in AnyOfValidator
This MR resets the matched node state for each sub-schema of the `anyOf`. It also sets the matched node state at the end based on errors present. 


Patch:
diff --git a/src/main/java/com/networknt/schema/AnyOfValidator.java b/src/main/java/com/networknt/schema/AnyOfValidator.java
index 461580e..4bcf122 100644
--- a/src/main/java/com/networknt/schema/AnyOfValidator.java
+++ b/src/main/java/com/networknt/schema/AnyOfValidator.java
@@ -60,6 +60,8 @@ public class AnyOfValidator extends BaseJsonValidator implements JsonValidator {
             validationContext.enterDiscriminatorContext(this.discriminatorContext, at);
         }
 
+        boolean initialHasMatchedNode = state.hasMatchedNode();
+
         Set<ValidationMessage> allErrors = new LinkedHashSet<ValidationMessage>();
         String typeValidatorName = "anyOf/type";
 
@@ -72,6 +74,7 @@ public class AnyOfValidator extends BaseJsonValidator implements JsonValidator {
         try {
             int numberOfValidSubSchemas = 0;
             for (JsonSchema schema : schemas) {
+                state.setMatchedNode(initialHasMatchedNode);
                 Set<ValidationMessage> errors = new HashSet<>();
                 if (schema.getValidators().containsKey(typeValidatorName)) {
                     TypeValidator typeValidator = ((TypeValidator) schema.getValidators().get(typeValidatorName));
@@ -87,7 +90,7 @@ public class AnyOfValidator extends BaseJsonValidator implements JsonValidator {
                 } else {
                     errors = schema.walk(node, rootNode, at, true);
                 }
-                
+
                 // check if any validation errors have occurred
                 if (errors.isEmpty()) {
                     // check whether there are no errors HOWEVER we have validated the exact validator
@@ -96,8 +99,8 @@ public class AnyOfValidator extends BaseJsonValidator implements JsonValidator {
                     }
                     // we found a valid subschema, so increase counter
                     numberOfValidSubSchemas++;
-                }                
-                
+                }
+
                 if (errors.isEmpty() && (!this.validationContext.getConfig().isOpenAPI3StyleDiscriminators())) {
                     // Clear all errors.
                     allErrors.clear();
@@ -137,6 +140,7 @@ public class AnyOfValidator extends BaseJsonValidator implements JsonValidator {
             }
             if (allErrors.isEmpty()) {
                 addEvaluatedProperties(backupEvaluatedProperties);
+                state.setMatchedNode(true);
             } else {
                 CollectorContext.getInstance().add(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES, backupEvaluatedProperties);
             }


Test patch:
diff --git a/src/test/java/com/networknt/schema/Issue606Test.java b/src/test/java/com/networknt/schema/Issue606Test.java
new file mode 100644
index 0000000..d018f17
--- /dev/null
+++ b/src/test/java/com/networknt/schema/Issue606Test.java
@@ -0,0 +1,34 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+import java.util.Set;
+
+public class Issue606Test {
+    protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) {
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+        return factory.getSchema(schemaContent);
+    }
+
+    protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode node = mapper.readTree(content);
+        return node;
+    }
+
+    @Test
+    public void shouldWorkV7() throws Exception {
+        String schemaPath = "/schema/issue606-v7.json";
+        String dataPath = "/data/issue606.json";
+        InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
+        JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
+        InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
+        JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
+        Set<ValidationMessage> errors = schema.validate(node);
+        Assertions.assertEquals(0, errors.size());
+    }
+}
diff --git a/src/test/resources/data/issue606.json b/src/test/resources/data/issue606.json
new file mode 100644
index 0000000..f3ee32f
--- /dev/null
+++ b/src/test/resources/data/issue606.json
@@ -0,0 +1,8 @@
+{
+    "V": [
+        {
+            "A": "foo",
+            "B": "bar"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/src/test/resources/schema/issue606-v7.json b/src/test/resources/schema/issue606-v7.json
new file mode 100644
index 0000000..f026907
--- /dev/null
+++ b/src/test/resources/schema/issue606-v7.json
@@ -0,0 +1,73 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+    "properties": {
+        "V": {
+            "type": "array",
+            "items": {
+                "oneOf": [
+                    {
+                        "type": "object",
+                        "properties": {
+                            "X": {
+                                "type": "string"
+                            }
+                        },
+                        "required": [
+                            "X"
+                        ],
+                        "additionalProperties": false
+                    },
+                    {
+                        "type": "object",
+                        "properties": {
+                            "A": {
+                                "type": "string"
+                            },
+                            "B": {
+                                "type": "string"
+                            },
+                            "C": {
+                                "type": "string"
+                            }
+                        },
+                        "anyOf": [
+                            {
+                                "properties": {
+                                    "origin": {
+                                        "const": "not-present"
+                                    }
+                                },
+                                "required": [
+                                    "A",
+                                    "C"
+                                ]
+                            },
+                            {
+                                "properties": {
+                                    "origin": {
+                                        "const": "not-present-either"
+                                    }
+                                },
+                                "required": [
+                                    "A",
+                                    "B"
+                                ]
+                            }
+                        ],
+                        "additionalProperties": false,
+                        "required": [
+                            "A"
+                        ],
+                        "not": {
+                            "type": "object",
+                            "required": [
+                                "X"
+                            ]
+                        }
+                    }
+                ]
+            }
+        }
+    }
+}
\ No newline at end of file