Repository: networknt/json-schema-validator
Pull request #146 (2019-05-17 15:32:32)

Fix/#145 array query parameter
Related issue: https://github.com/networknt/json-schema-validator/issues/145


Patch:
diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java
index a851ff9..7c3b148 100644
--- a/src/main/java/com/networknt/schema/ItemsValidator.java
+++ b/src/main/java/com/networknt/schema/ItemsValidator.java
@@ -1,99 +1,105 @@
-/*
- * Copyright (c) 2016 Network New Technologies Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.networknt.schema;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-public class ItemsValidator extends BaseJsonValidator implements JsonValidator {
-    private static final Logger logger = LoggerFactory.getLogger(ItemsValidator.class);
-    private static final String PROPERTY_ADDITIONAL_ITEMS = "additionalItems";
-
-    private JsonSchema schema;
-    private List<JsonSchema> tupleSchema;
-    private boolean additionalItems = true;
-    private JsonSchema additionalSchema;
-
-    public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext);
-        if (schemaNode.isObject()) {
-            schema = new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode, parentSchema);
-        } else {
-            tupleSchema = new ArrayList<JsonSchema>();
-            for (JsonNode s : schemaNode) {
-                tupleSchema.add(new JsonSchema(validationContext, getValidatorType().getValue(), s, parentSchema));
-            }
-
-            JsonNode addItemNode = getParentSchema().getSchemaNode().get(PROPERTY_ADDITIONAL_ITEMS);
-            if (addItemNode != null) {
-                if (addItemNode.isBoolean()) {
-                    additionalItems = addItemNode.asBoolean();
-                } else if (addItemNode.isObject()) {
-                    additionalSchema = new JsonSchema(validationContext, addItemNode);
-                }
-            }
-        }
-
-        parseErrorCode(getValidatorType().getErrorCodeKey());
-    }
-
-    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
-        debug(logger, node, rootNode, at);
-
-        Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
-
-        if (!node.isArray()) {
-            // ignores non-arrays
-            return errors;
-        }
-
-        int i = 0;
-        for (JsonNode n : node) {
-            if (schema != null) {
-                // validate with item schema (the whole array has the same item
-                // schema)
-                errors.addAll(schema.validate(n, rootNode, at + "[" + i + "]"));
-            }
-
-            if (tupleSchema != null) {
-                if (i < tupleSchema.size()) {
-                    // validate against tuple schema
-                    errors.addAll(tupleSchema.get(i).validate(n, rootNode, at + "[" + i + "]"));
-                } else {
-                    if (additionalSchema != null) {
-                        // validate against additional item schema
-                        errors.addAll(additionalSchema.validate(n, rootNode, at + "[" + i + "]"));
-                    } else if (!additionalItems) {
-                        // no additional item allowed, return error
-                        errors.add(buildValidationMessage(at, "" + i));
-                    }
-                }
-            }
-
-            i++;
-        }
-        return Collections.unmodifiableSet(errors);
-    }
-
-}
+/*
+ * Copyright (c) 2016 Network New Technologies Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ItemsValidator extends BaseJsonValidator implements JsonValidator {
+    private static final Logger logger = LoggerFactory.getLogger(ItemsValidator.class);
+    private static final String PROPERTY_ADDITIONAL_ITEMS = "additionalItems";
+
+    private JsonSchema schema;
+    private List<JsonSchema> tupleSchema;
+    private boolean additionalItems = true;
+    private JsonSchema additionalSchema;
+
+    public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext);
+        if (schemaNode.isObject()) {
+            schema = new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode, parentSchema);
+        } else {
+            tupleSchema = new ArrayList<JsonSchema>();
+            for (JsonNode s : schemaNode) {
+                tupleSchema.add(new JsonSchema(validationContext, getValidatorType().getValue(), s, parentSchema));
+            }
+
+            JsonNode addItemNode = getParentSchema().getSchemaNode().get(PROPERTY_ADDITIONAL_ITEMS);
+            if (addItemNode != null) {
+                if (addItemNode.isBoolean()) {
+                    additionalItems = addItemNode.asBoolean();
+                } else if (addItemNode.isObject()) {
+                    additionalSchema = new JsonSchema(validationContext, addItemNode);
+                }
+            }
+        }
+
+        parseErrorCode(getValidatorType().getErrorCodeKey());
+    }
+
+    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
+        debug(logger, node, rootNode, at);
+
+        Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
+
+        if (!node.isArray() && !config.isTypeLoose()) {
+            // ignores non-arrays
+            return errors;
+        }
+        if (node.isArray()) {
+            int i = 0;
+            for (JsonNode n : node) {
+                doValidate(errors, i, n, rootNode, at);
+                i++;
+            }
+        } else {
+            doValidate(errors, 0, node, rootNode, at);
+        }
+        return Collections.unmodifiableSet(errors);
+    }
+
+    private void doValidate(Set<ValidationMessage> errors, int i, JsonNode node, JsonNode rootNode, String at) {
+        if (schema != null) {
+            // validate with item schema (the whole array has the same item
+            // schema)
+            errors.addAll(schema.validate(node, rootNode, at + "[" + i + "]"));
+        }
+
+        if (tupleSchema != null) {
+            if (i < tupleSchema.size()) {
+                // validate against tuple schema
+                errors.addAll(tupleSchema.get(i).validate(node, rootNode, at + "[" + i + "]"));
+            } else {
+                if (additionalSchema != null) {
+                    // validate against additional item schema
+                    errors.addAll(additionalSchema.validate(node, rootNode, at + "[" + i + "]"));
+                } else if (!additionalItems) {
+                    // no additional item allowed, return error
+                    errors.add(buildValidationMessage(at, "" + i));
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/com/networknt/schema/MaxItemsValidator.java b/src/main/java/com/networknt/schema/MaxItemsValidator.java
index f7b5dcb..dd17790 100644
--- a/src/main/java/com/networknt/schema/MaxItemsValidator.java
+++ b/src/main/java/com/networknt/schema/MaxItemsValidator.java
@@ -1,52 +1,56 @@
-/*
- * Copyright (c) 2016 Network New Technologies Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.networknt.schema;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collections;
-import java.util.Set;
-
-public class MaxItemsValidator extends BaseJsonValidator implements JsonValidator {
-    private static final Logger logger = LoggerFactory.getLogger(MaxItemsValidator.class);
-
-    private int max = 0;
-
-    public MaxItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_ITEMS, validationContext);
-        if (schemaNode.isIntegralNumber()) {
-            max = schemaNode.intValue();
-        }
-
-        parseErrorCode(getValidatorType().getErrorCodeKey());
-    }
-
-    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
-        debug(logger, node, rootNode, at);
-
-        if (node.isArray()) {
-            if (node.size() > max) {
-                return Collections.singleton(buildValidationMessage(at, "" + max));
-            }
-        }
-
-        return Collections.emptySet();
-    }
-
-}
+/*
+ * Copyright (c) 2016 Network New Technologies Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class MaxItemsValidator extends BaseJsonValidator implements JsonValidator {
+    private static final Logger logger = LoggerFactory.getLogger(MaxItemsValidator.class);
+
+    private int max = 0;
+
+    public MaxItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_ITEMS, validationContext);
+        if (schemaNode.isIntegralNumber()) {
+            max = schemaNode.intValue();
+        }
+
+        parseErrorCode(getValidatorType().getErrorCodeKey());
+    }
+
+    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
+        debug(logger, node, rootNode, at);
+
+        if (node.isArray()) {
+            if (node.size() > max) {
+                return Collections.singleton(buildValidationMessage(at, "" + max));
+            }
+        } else if (config.isTypeLoose()) {
+            if (1 > max) {
+                return Collections.singleton(buildValidationMessage(at, "" + max));
+            }
+        }
+
+        return Collections.emptySet();
+    }
+
+}
diff --git a/src/main/java/com/networknt/schema/MinItemsValidator.java b/src/main/java/com/networknt/schema/MinItemsValidator.java
index 49a4c32..b58c5c0 100644
--- a/src/main/java/com/networknt/schema/MinItemsValidator.java
+++ b/src/main/java/com/networknt/schema/MinItemsValidator.java
@@ -1,52 +1,56 @@
-/*
- * Copyright (c) 2016 Network New Technologies Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.networknt.schema;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collections;
-import java.util.Set;
-
-public class MinItemsValidator extends BaseJsonValidator implements JsonValidator {
-    private static final Logger logger = LoggerFactory.getLogger(MinItemsValidator.class);
-
-    private int min = 0;
-
-    public MinItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_ITEMS, validationContext);
-        if (schemaNode.isIntegralNumber()) {
-            min = schemaNode.intValue();
-        }
-
-        parseErrorCode(getValidatorType().getErrorCodeKey());
-    }
-
-    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
-        debug(logger, node, rootNode, at);
-
-        if (node.isArray()) {
-            if (node.size() < min) {
-                return Collections.singleton(buildValidationMessage(at, "" + min));
-            }
-        }
-
-        return Collections.emptySet();
-    }
-
-}
+/*
+ * Copyright (c) 2016 Network New Technologies Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class MinItemsValidator extends BaseJsonValidator implements JsonValidator {
+    private static final Logger logger = LoggerFactory.getLogger(MinItemsValidator.class);
+
+    private int min = 0;
+
+    public MinItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_ITEMS, validationContext);
+        if (schemaNode.isIntegralNumber()) {
+            min = schemaNode.intValue();
+        }
+
+        parseErrorCode(getValidatorType().getErrorCodeKey());
+    }
+
+    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
+        debug(logger, node, rootNode, at);
+
+        if (node.isArray()) {
+            if (node.size() < min) {
+                return Collections.singleton(buildValidationMessage(at, "" + min));
+            }
+        } else if (config.isTypeLoose()) {
+            if (1 < min) {
+                return Collections.singleton(buildValidationMessage(at, "" + min));
+            }
+        }
+
+        return Collections.emptySet();
+    }
+
+}
diff --git a/src/main/java/com/networknt/schema/TypeValidator.java b/src/main/java/com/networknt/schema/TypeValidator.java
index 8fd2f06..55451ef 100644
--- a/src/main/java/com/networknt/schema/TypeValidator.java
+++ b/src/main/java/com/networknt/schema/TypeValidator.java
@@ -74,6 +74,10 @@ public class TypeValidator extends BaseJsonValidator implements JsonValidator {
                 return true;
             }
             if(config.isTypeLoose()) {
+                // if typeLoose is true, everything can be a size 1 array
+                if (schemaType == JsonType.ARRAY) {
+                    return true;
+                }
                 if (nodeType == JsonType.STRING) {
                     if(schemaType == JsonType.INTEGER) {
                         if(isInteger(node.textValue())) {


Test patch:
diff --git a/src/test/java/com/networknt/schema/JsonSchemaTest.java b/src/test/java/com/networknt/schema/JsonSchemaTest.java
index 97f38f3..2a4e82e 100644
--- a/src/test/java/com/networknt/schema/JsonSchemaTest.java
+++ b/src/test/java/com/networknt/schema/JsonSchemaTest.java
@@ -83,7 +83,7 @@ public class JsonSchemaTest {
                     JsonNode typeLooseNode = test.get("isTypeLoose");
                     // Configure the schemaValidator to set typeLoose's value based on the test file,
                     // if test file do not contains typeLoose flag, use default value: true.
-                    config.setTypeLoose((typeLooseNode == null) ? true : typeLooseNode.asBoolean());
+                    config.setTypeLoose((typeLooseNode == null) ? false : typeLooseNode.asBoolean());
                     JsonSchema schema = validatorFactory.getSchema(testCase.get("schema"), config);
                     List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
 
diff --git a/src/test/resources/tests/additionalItems.json b/src/test/resources/tests/additionalItems.json
index 521745c..40abb35 100644
--- a/src/test/resources/tests/additionalItems.json
+++ b/src/test/resources/tests/additionalItems.json
@@ -78,5 +78,39 @@
                 "valid": true
             }
         ]
+    },
+    {
+        "description": "additionalItems for size 1 array",
+        "schema": {
+            "items": {},
+            "additionalItems": false
+        },
+        "tests": [
+            {
+                "description":
+                "items defaults to empty schema so everything is valid",
+                "data": "string",
+                "isTypeLoose": true,
+                "valid": true
+            }
+        ]
+    },
+    {
+        "description": "additionalItems are allowed by default for size 1 array",
+        "schema": {"items": [{"type": "integer"}]},
+        "tests": [
+            {
+                "description": "valid size 1 array match the schema",
+                "data": 1,
+                "isTypeLoose": true,
+                "valid": true
+            },
+            {
+                "description": "invalid size 1 array doesn't match the schema",
+                "data": "string",
+                "isTypeLoose": true,
+                "valid": false
+            }
+        ]
     }
 ]
diff --git a/src/test/resources/tests/items.json b/src/test/resources/tests/items.json
index f5e18a1..ce34898 100644
--- a/src/test/resources/tests/items.json
+++ b/src/test/resources/tests/items.json
@@ -42,5 +42,27 @@
                 "valid": false
             }
         ]
+    },
+    {
+        "description": "an typeLoose array of schemas for items",
+        "schema": {
+            "items": [
+                {"type": "integer"}
+            ]
+        },
+        "tests": [
+            {
+                "description": "correct types",
+                "data": 1,
+                "isTypeLoose": true,
+                "valid": true
+            },
+            {
+                "description": "wrong types",
+                "data": "foo",
+                "isTypeLoose": true,
+                "valid": false
+            }
+        ]
     }
 ]
diff --git a/src/test/resources/tests/maxItems.json b/src/test/resources/tests/maxItems.json
index 3b53a6b..73fecc3 100644
--- a/src/test/resources/tests/maxItems.json
+++ b/src/test/resources/tests/maxItems.json
@@ -24,5 +24,17 @@
                 "valid": true
             }
         ]
+    },
+    {
+        "description": "maxItems validation for size 1 array",
+        "schema": {"maxItems": 0},
+        "tests": [
+            {
+                "description": "too long is invalid",
+                "data": 1,
+                "isTypeLoose": true,
+                "valid": false
+            }
+        ]
     }
 ]
diff --git a/src/test/resources/tests/minItems.json b/src/test/resources/tests/minItems.json
index ed51188..416dfb4 100644
--- a/src/test/resources/tests/minItems.json
+++ b/src/test/resources/tests/minItems.json
@@ -24,5 +24,17 @@
                 "valid": true
             }
         ]
+    },
+    {
+        "description": "minItems validation for size 1 array",
+        "schema": {"minItems": 2},
+        "tests": [
+            {
+                "description": "shorter is invalid",
+                "data": 1,
+                "isTypeLoose": true,
+                "valid": false
+            }
+        ]
     }
 ]
diff --git a/src/test/resources/tests/type.json b/src/test/resources/tests/type.json
index 9c58ab5..ffdf906 100644
--- a/src/test/resources/tests/type.json
+++ b/src/test/resources/tests/type.json
@@ -21,6 +21,7 @@
             {
                 "description": "a string that can be converted to integer is an integer",
                 "data": "1",
+                "isTypeLoose": true,
                 "valid": true
             },
             {
@@ -67,6 +68,7 @@
             {
                 "description": "a string that can be converted to number is a number",
                 "data": "1",
+                "isTypeLoose": true,
                 "valid": true
             },
             {
@@ -216,6 +218,48 @@
                 "description": "null is not an array",
                 "data": null,
                 "valid": false
+            },
+            {
+                "description": "an typeLoose integer is an array",
+                "data": 1,
+                "isTypeLoose": true,
+                "valid": true
+            },
+            {
+                "description": "a typeLoose float is an array",
+                "data": 1.1,
+                "isTypeLoose": true,
+                "valid": true
+            },
+            {
+                "description": "a typeLoose string is an array",
+                "data": "foo",
+                "isTypeLoose": true,
+                "valid": true
+            },
+            {
+                "description": "an typeLoose object is an array",
+                "data": {},
+                "isTypeLoose": true,
+                "valid": true
+            },
+            {
+                "description": "an typeLoose array is an array",
+                "data": [],
+                "isTypeLoose": true,
+                "valid": true
+            },
+            {
+                "description": "a typeLoose boolean is an array",
+                "data": true,
+                "isTypeLoose": true,
+                "valid": true
+            },
+            {
+                "description": "typeLoose null is an array",
+                "data": null,
+                "isTypeLoose": true,
+                "valid": true
             }
         ]
     },