Repository: networknt/json-schema-validator
Pull request #113 (2019-01-17 19:24:06)

Fixed validation for path parameters and query parameters
@NicholasAzar @stevehu Since the RequestValidator is built on json-schema-validator. There is another PR related to this issue can be found in networknt/light-rest-4j repo. These changes in different repo contributions to the same issue.

RFC which specifies the details of fix can be found here: https://github.com/networknt/light-rfcs/blob/master/light-rest-4j/0001-openapi-validator-bug-fix.md


Patch:
diff --git a/src/main/java/com/networknt/schema/EnumValidator.java b/src/main/java/com/networknt/schema/EnumValidator.java
index ba86f6c..87b3453 100644
--- a/src/main/java/com/networknt/schema/EnumValidator.java
+++ b/src/main/java/com/networknt/schema/EnumValidator.java
@@ -1,75 +1,92 @@
-/*
- * 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.LinkedHashSet;
-import java.util.HashSet;
-import java.util.Set;
-
-public class EnumValidator extends BaseJsonValidator implements JsonValidator {
-    private static final Logger logger = LoggerFactory.getLogger(EnumValidator.class);
-
-    private final Set<JsonNode> nodes;
-    private final String error;
-
-    public EnumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
-            super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ENUM, validationContext);
-
-        if (schemaNode != null && schemaNode.isArray()) {
-            nodes = new HashSet<JsonNode>();
-            StringBuilder sb = new StringBuilder();
-
-            sb.append('[');
-            String separator = "";
-
-            for (JsonNode n : schemaNode) {
-                nodes.add(n);
-
-                sb.append(separator);
-                sb.append(n.asText());
-                separator = ", ";
-            }
-
-            sb.append(']');
-
-            error = sb.toString();
-        } else {
-            nodes = Collections.emptySet();
-            error = "[none]";
-        }
-
-        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 (!nodes.contains(node)) {
-            errors.add(buildValidationMessage(at, error));
-        }
-
-        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.Collections;
+import java.util.LinkedHashSet;
+import java.util.HashSet;
+import java.util.Set;
+
+public class EnumValidator extends BaseJsonValidator implements JsonValidator {
+    private static final Logger logger = LoggerFactory.getLogger(EnumValidator.class);
+
+    private final Set<JsonNode> nodes;
+    private final String error;
+
+    public EnumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+            super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ENUM, validationContext);
+
+        if (schemaNode != null && schemaNode.isArray()) {
+            nodes = new HashSet<JsonNode>();
+            StringBuilder sb = new StringBuilder();
+
+            sb.append('[');
+            String separator = "";
+
+            for (JsonNode n : schemaNode) {
+                nodes.add(n);
+
+                sb.append(separator);
+                sb.append(n.asText());
+                separator = ", ";
+            }
+
+            sb.append(']');
+
+            error = sb.toString();
+        } else {
+            nodes = Collections.emptySet();
+            error = "[none]";
+        }
+
+        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 (!nodes.contains(node) && !(config.isTypeLoose() && isTypeLooseContainsInEnum(node))) {
+            errors.add(buildValidationMessage(at, error));
+        }
+
+        return Collections.unmodifiableSet(errors);
+    }
+
+    /**
+     * Check whether enum contains the value of the JsonNode if the typeLoose is enabled.
+     * @param node JsonNode to check
+     */
+    private boolean isTypeLooseContainsInEnum(JsonNode node) {
+        if (TypeFactory.getValueNodeType(node) == JsonType.STRING) {
+            String nodeText = node.textValue();
+            for (JsonNode n : nodes) {
+                String value = n.asText();
+                if (value != null && value.equals(nodeText)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/src/main/java/com/networknt/schema/JsonMetaSchema.java b/src/main/java/com/networknt/schema/JsonMetaSchema.java
index 049af19..8a8eacd 100644
--- a/src/main/java/com/networknt/schema/JsonMetaSchema.java
+++ b/src/main/java/com/networknt/schema/JsonMetaSchema.java
@@ -46,7 +46,7 @@ public class JsonMetaSchema {
         public static final List<Format> BUILTIN_FORMATS = new ArrayList<Format>();
         static {
             BUILTIN_FORMATS.add(pattern("date-time", 
-                    "^\\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(0?[1-9]|[12][0-9]|3[01])[tT ]\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?([zZ]|[+-]\\d{2}:\\d{2})$"));
+                    "^\\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(0?[1-9]|[12][0-9]|3[01])[tT ]\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?([zZ]?|[+-]\\d{2}:\\d{2})$"));
             BUILTIN_FORMATS.add(pattern("date", "^\\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(0?[1-9]|[12][0-9]|3[01])$"));
             BUILTIN_FORMATS.add(pattern("time", "^\\d{2}:\\d{2}:\\d{2}$"));
             BUILTIN_FORMATS.add(pattern("email", "^\\S+@\\S+$"));
diff --git a/src/main/java/com/networknt/schema/MaximumValidator.java b/src/main/java/com/networknt/schema/MaximumValidator.java
index a8d5157..193b2fd 100644
--- a/src/main/java/com/networknt/schema/MaximumValidator.java
+++ b/src/main/java/com/networknt/schema/MaximumValidator.java
@@ -1,70 +1,70 @@
-/*
- * 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 MaximumValidator extends BaseJsonValidator implements JsonValidator {
-    private static final Logger logger = LoggerFactory.getLogger(MaximumValidator.class);
-    private static final String PROPERTY_EXCLUSIVE_MAXIMUM = "exclusiveMaximum";
-
-    private double maximum;
-    private boolean excludeEqual = false;
-
-    public MaximumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
-
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAXIMUM, validationContext);
-        if (schemaNode.isNumber()) {
-            maximum = schemaNode.doubleValue();
-        } else {
-            throw new JsonSchemaException("maximum value is not a number");
-        }
-
-        JsonNode exclusiveMaximumNode = getParentSchema().getSchemaNode().get(PROPERTY_EXCLUSIVE_MAXIMUM);
-        if (exclusiveMaximumNode != null && exclusiveMaximumNode.isBoolean()) {
-            excludeEqual = exclusiveMaximumNode.booleanValue();
-        }
-
-        parseErrorCode(getValidatorType().getErrorCodeKey());
-    }
-
-    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
-        debug(logger, node, rootNode, at);
-
-        if (!node.isNumber()) {
-            // maximum only applies to numbers
-            return Collections.emptySet();
-        }
-
-        String fieldType = this.getNodeFieldType();
-
-        double value = node.doubleValue();
-        if (greaterThan(value, maximum) || (excludeEqual && equals(value, maximum))) {
-            if (JsonType.INTEGER.toString().equals(fieldType)) {
-                return Collections.singleton(buildValidationMessage(at, "" + (int)maximum));
-            }
-            return Collections.singleton(buildValidationMessage(at, "" + maximum));
-        }
-        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 MaximumValidator extends BaseJsonValidator implements JsonValidator {
+    private static final Logger logger = LoggerFactory.getLogger(MaximumValidator.class);
+    private static final String PROPERTY_EXCLUSIVE_MAXIMUM = "exclusiveMaximum";
+
+    private double maximum;
+    private boolean excludeEqual = false;
+
+    public MaximumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAXIMUM, validationContext);
+        if (schemaNode.isNumber()) {
+            maximum = schemaNode.doubleValue();
+        } else {
+            throw new JsonSchemaException("maximum value is not a number");
+        }
+
+        JsonNode exclusiveMaximumNode = getParentSchema().getSchemaNode().get(PROPERTY_EXCLUSIVE_MAXIMUM);
+        if (exclusiveMaximumNode != null && exclusiveMaximumNode.isBoolean()) {
+            excludeEqual = exclusiveMaximumNode.booleanValue();
+        }
+
+        parseErrorCode(getValidatorType().getErrorCodeKey());
+    }
+
+    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
+        debug(logger, node, rootNode, at);
+
+        if (!TypeValidator.isNumber(node, config.isTypeLoose())) {
+            // maximum only applies to numbers
+            return Collections.emptySet();
+        }
+
+        String fieldType = this.getNodeFieldType();
+
+        double value = node.asDouble();
+        if (greaterThan(value, maximum) || (excludeEqual && equals(value, maximum))) {
+            if (JsonType.INTEGER.toString().equals(fieldType)) {
+                return Collections.singleton(buildValidationMessage(at, "" + (int)maximum));
+            }
+            return Collections.singleton(buildValidationMessage(at, "" + maximum));
+        }
+        return Collections.emptySet();
+    }
+
+}
diff --git a/src/main/java/com/networknt/schema/MinimumValidator.java b/src/main/java/com/networknt/schema/MinimumValidator.java
index db40f4b..9674990 100644
--- a/src/main/java/com/networknt/schema/MinimumValidator.java
+++ b/src/main/java/com/networknt/schema/MinimumValidator.java
@@ -1,68 +1,68 @@
-/*
- * 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 MinimumValidator extends BaseJsonValidator implements JsonValidator {
-    private static final Logger logger = LoggerFactory.getLogger(MinimumValidator.class);
-    private static final String PROPERTY_EXCLUSIVE_MINIMUM = "exclusiveMinimum";
-
-    private double minimum;
-    private boolean excluded = false;
-
-    public MinimumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MINIMUM, validationContext);
-        if (schemaNode.isNumber()) {
-            minimum = schemaNode.doubleValue();
-        } else {
-            throw new JsonSchemaException("minimum value is not a number");
-        }
-
-        JsonNode exclusiveMinimumNode = getParentSchema().getSchemaNode().get(PROPERTY_EXCLUSIVE_MINIMUM);
-        if (exclusiveMinimumNode != null && exclusiveMinimumNode.isBoolean()) {
-            excluded = exclusiveMinimumNode.booleanValue();
-        }
-
-        parseErrorCode(getValidatorType().getErrorCodeKey());
-    }
-
-    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
-        debug(logger, node, rootNode, at);
-
-        if (!node.isNumber()) {
-            // minimum only applies to numbers
-            return Collections.emptySet();
-        }
-        String fieldType = this.getNodeFieldType();
-
-        double value = node.doubleValue();
-        if (lessThan(value, minimum) || (excluded && equals(value, minimum))) {
-            if (JsonType.INTEGER.toString().equals(fieldType)) {
-                return Collections.singleton(buildValidationMessage(at, "" + (int) minimum));
-            }
-            return Collections.singleton(buildValidationMessage(at, "" + minimum));
-        }
-        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 MinimumValidator extends BaseJsonValidator implements JsonValidator {
+    private static final Logger logger = LoggerFactory.getLogger(MinimumValidator.class);
+    private static final String PROPERTY_EXCLUSIVE_MINIMUM = "exclusiveMinimum";
+
+    private double minimum;
+    private boolean excluded = false;
+
+    public MinimumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MINIMUM, validationContext);
+        if (schemaNode.isNumber()) {
+            minimum = schemaNode.doubleValue();
+        } else {
+            throw new JsonSchemaException("minimum value is not a number");
+        }
+
+        JsonNode exclusiveMinimumNode = getParentSchema().getSchemaNode().get(PROPERTY_EXCLUSIVE_MINIMUM);
+        if (exclusiveMinimumNode != null && exclusiveMinimumNode.isBoolean()) {
+            excluded = exclusiveMinimumNode.booleanValue();
+        }
+
+        parseErrorCode(getValidatorType().getErrorCodeKey());
+    }
+
+    public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
+        debug(logger, node, rootNode, at);
+
+        if (!TypeValidator.isNumber(node, config.isTypeLoose())) {
+            // minimum only applies to numbers
+            return Collections.emptySet();
+        }
+        String fieldType = this.getNodeFieldType();
+
+        double value = node.asDouble();
+        if (lessThan(value, minimum) || (excluded && equals(value, minimum))) {
+            if (JsonType.INTEGER.toString().equals(fieldType)) {
+                return Collections.singleton(buildValidationMessage(at, "" + (int) minimum));
+            }
+            return Collections.singleton(buildValidationMessage(at, "" + minimum));
+        }
+        return Collections.emptySet();
+    }
+
+}
diff --git a/src/main/java/com/networknt/schema/TypeValidator.java b/src/main/java/com/networknt/schema/TypeValidator.java
index 343ccd6..7dd74d7 100644
--- a/src/main/java/com/networknt/schema/TypeValidator.java
+++ b/src/main/java/com/networknt/schema/TypeValidator.java
@@ -147,4 +147,21 @@ public class TypeValidator extends BaseJsonValidator implements JsonValidator {
         }
         return true;
     }
+
+    /**
+     * Check if the type of the JsonNode's value is number based on the
+     * status of typeLoose flag.
+     * @param node the JsonNode to check
+     * @param isTypeLoose The flag to show whether typeLoose is enabled
+     */
+    public static boolean isNumber(JsonNode node, boolean isTypeLoose) {
+        if (node.isNumber()) {
+            return true;
+        } else if (isTypeLoose) {
+            if (TypeFactory.getValueNodeType(node) == JsonType.STRING) {
+                return isNumeric(node.textValue());
+            }
+        }
+        return false;
+    }
 }


Test patch:
diff --git a/src/test/java/com/networknt/schema/JsonSchemaTest.java b/src/test/java/com/networknt/schema/JsonSchemaTest.java
index 04b2027..1786c6b 100644
--- a/src/test/java/com/networknt/schema/JsonSchemaTest.java
+++ b/src/test/java/com/networknt/schema/JsonSchemaTest.java
@@ -1,292 +1,298 @@
-/*
- * 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 com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import io.undertow.Undertow;
-import io.undertow.server.handlers.resource.FileResourceManager;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-
-import static io.undertow.Handlers.resource;
-
-public class JsonSchemaTest {
-    protected ObjectMapper mapper = new ObjectMapper();
-    protected JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance()).objectMapper(mapper).build();
-    protected static Undertow server = null;
-
-    public JsonSchemaTest() {
-    }
-
-    @BeforeClass
-    public static void setUp() {
-        if(server == null) {
-            server = Undertow.builder()
-                    .addHttpListener(1234, "localhost")
-                    .setHandler(resource(new FileResourceManager(
-                            new File("./src/test/resources/tests"), 100)))
-                    .build();
-            server.start();
-        }
-    }
-
-    @AfterClass
-    public static void tearDown() throws Exception {
-        if(server != null) {
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException ignored) {
-
-            }
-            server.stop();
-        }
-    }
-
-    private void runTestFile(String testCaseFile) throws Exception {
-        InputStream in = Thread.currentThread().getContextClassLoader()
-                .getResourceAsStream(testCaseFile);
-        ArrayNode testCases = (ArrayNode) mapper.readTree(in);
-
-        for (int j = 0; j < testCases.size(); j++) {
-            try {
-                JsonNode testCase = testCases.get(j);
-                JsonSchema schema = validatorFactory.getSchema(testCase.get("schema"));
-                ArrayNode testNodes = (ArrayNode) testCase.get("tests");
-                for (int i = 0; i < testNodes.size(); i++) {
-                    JsonNode test = testNodes.get(i);
-                    JsonNode node = test.get("data");
-                    List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
-
-                    errors.addAll(schema.validate(node));
-
-                    if (test.get("valid").asBoolean()) {
-                        if (!errors.isEmpty()) {
-                            System.out.println("---- test case filed ----");
-                            System.out.println("schema: " + schema.toString());
-                            System.out.println("data: " + test.get("data"));
-                        }
-                        Assert.assertEquals(0, errors.size());
-                    } else {
-                        if (errors.isEmpty()) {
-                            System.out.println("---- test case filed ----");
-                            System.out.println("schema: " + schema);
-                            System.out.println("data: " + test.get("data"));
-                        }
-                        Assert.assertEquals(false, errors.isEmpty());
-                    }
-                }
-            } catch (JsonSchemaException e) {
-                System.out.println("Bypass validation due to invalid schema: " + e.getMessage());
-            }
-        }
-    }
-
-    @Test(/*expected = java.lang.StackOverflowError.class*/)
-    public void testLoadingWithId() throws Exception {
-        URL url = new URL("http://localhost:1234/self_ref/selfRef.json");
-        JsonNode schemaJson = mapper.readTree(url);
-        JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
-        @SuppressWarnings("unused")
-        JsonSchema schema = factory.getSchema(schemaJson);
-    }
-
-    @Test
-    public void testBignumValidator() throws Exception {
-        runTestFile("tests/optional/bignum.json");
-    }
-
-    @Test
-    public void testFormatValidator() throws Exception {
-        runTestFile("tests/optional/format.json");
-    }
-
-    @Test
-    public void testComplexSchema() throws Exception {
-        runTestFile("tests/optional/complex.json");
-    }
-
-    @Test
-    public void testZeroTerminatedFloatsValidator() throws Exception {
-        runTestFile("tests/optional/zeroTerminatedFloats.json");
-    }
-
-    @Test
-    public void testAdditionalItemsValidator() throws Exception {
-        runTestFile("tests/additionalItems.json");
-    }
-
-    @Test
-    public void testAdditionalPropertiesValidator() throws Exception {
-        runTestFile("tests/additionalProperties.json");
-    }
-
-    @Test
-    public void testAllOfValidator() throws Exception {
-        runTestFile("tests/allOf.json");
-    }
-
-    @Test
-    public void testAnyOFValidator() throws Exception {
-        runTestFile("tests/anyOf.json");
-    }
-
-    @Test
-    public void testDefaultValidator() throws Exception {
-        runTestFile("tests/default.json");
-    }
-
-    @Test
-    public void testDefinitionsValidator() throws Exception {
-        runTestFile("tests/definitions.json");
-    }
-
-    @Test
-    public void testDependenciesValidator() throws Exception {
-        runTestFile("tests/dependencies.json");
-    }
-
-    @Test
-    public void testEnumValidator() throws Exception {
-        runTestFile("tests/enum.json");
-    }
-
-    @Test
-    public void testItemsValidator() throws Exception {
-        runTestFile("tests/items.json");
-    }
-
-    @Test
-    public void testMaximumValidator() throws Exception {
-        runTestFile("tests/maximum.json");
-    }
-
-    @Test
-    public void testMaxItemsValidator() throws Exception {
-        runTestFile("tests/maxItems.json");
-    }
-
-    @Test
-    public void testMaxLengthValidator() throws Exception {
-        runTestFile("tests/maxLength.json");
-    }
-
-    @Test
-    public void testMaxPropertiesValidator() throws Exception {
-        runTestFile("tests/maxProperties.json");
-    }
-
-    @Test
-    public void testMinimumValidator() throws Exception {
-        runTestFile("tests/minimum.json");
-    }
-
-    @Test
-    public void testMinItemsValidator() throws Exception {
-        runTestFile("tests/minItems.json");
-    }
-
-    @Test
-    public void testMinLengthValidator() throws Exception {
-        runTestFile("tests/minLength.json");
-    }
-
-    @Test
-    public void testMinPropertiesValidator() throws Exception {
-        runTestFile("tests/minProperties.json");
-    }
-
-    @Test
-    public void testMultipleOfValidator() throws Exception {
-        runTestFile("tests/multipleOf.json");
-    }
-
-    @Test
-    public void testNotValidator() throws Exception {
-        runTestFile("tests/not.json");
-    }
-
-    @Test
-    public void testOneOfValidator() throws Exception {
-        runTestFile("tests/oneOf.json");
-    }
-    
-    @Test
-    public void testPatternValidator() throws Exception {
-        runTestFile("tests/pattern.json");
-    }
-
-    @Test
-    public void testPatternPropertiesValidator() throws Exception {
-        runTestFile("tests/patternProperties.json");
-    }
-
-    @Test
-    public void testPropertiesValidator() throws Exception {
-        runTestFile("tests/properties.json");
-    }
-
-    @Test
-    public void testRefValidator() throws Exception {
-        runTestFile("tests/ref.json");
-    }
-
-    @Test
-    public void testRefRemoteValidator() throws Exception {
-        runTestFile("tests/refRemote.json");
-    }
-
-    @Test
-    public void testRequiredValidator() throws Exception {
-        runTestFile("tests/required.json");
-    }
-
-    @Test
-    public void testTypeValidator() throws Exception {
-        runTestFile("tests/type.json");
-    }
-
-    @Test
-    public void testUnionTypeValidator() throws Exception {
-        runTestFile("tests/union_type.json");
-    }
-
-    @Test
-    public void testUniqueItemsValidator() throws Exception {
-        runTestFile("tests/uniqueItems.json");
-    }
-    
-    @Test
-    public void testIdSchemaWithUrl() throws Exception {
-        runTestFile("tests/id_schema/property.json");
-    }
-
-    @Test
-    public void testSchemaFromClasspath() throws Exception {
-        runTestFile("tests/classpath/schema.json");
-    }
-
-}
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import io.undertow.Undertow;
+import io.undertow.server.handlers.resource.FileResourceManager;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import static io.undertow.Handlers.resource;
+
+public class JsonSchemaTest {
+    protected ObjectMapper mapper = new ObjectMapper();
+    protected JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance()).objectMapper(mapper).build();
+    protected static Undertow server = null;
+
+    public JsonSchemaTest() {
+    }
+
+    @BeforeClass
+    public static void setUp() {
+        if(server == null) {
+            server = Undertow.builder()
+                    .addHttpListener(1234, "localhost")
+                    .setHandler(resource(new FileResourceManager(
+                            new File("./src/test/resources/tests"), 100)))
+                    .build();
+            server.start();
+        }
+    }
+
+    @AfterClass
+    public static void tearDown() throws Exception {
+        if(server != null) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ignored) {
+
+            }
+            server.stop();
+        }
+    }
+
+    private void runTestFile(String testCaseFile) throws Exception {
+        InputStream in = Thread.currentThread().getContextClassLoader()
+                .getResourceAsStream(testCaseFile);
+        ArrayNode testCases = (ArrayNode) mapper.readTree(in);
+
+        for (int j = 0; j < testCases.size(); j++) {
+            try {
+                JsonNode testCase = testCases.get(j);
+                SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+
+                ArrayNode testNodes = (ArrayNode) testCase.get("tests");
+                for (int i = 0; i < testNodes.size(); i++) {
+                    JsonNode test = testNodes.get(i);
+                    JsonNode node = test.get("data");
+                    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());
+                    JsonSchema schema = validatorFactory.getSchema(testCase.get("schema"), config);
+                    List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
+
+                    errors.addAll(schema.validate(node));
+
+                    if (test.get("valid").asBoolean()) {
+                        if (!errors.isEmpty()) {
+                            System.out.println("---- test case filed ----");
+                            System.out.println("schema: " + schema.toString());
+                            System.out.println("data: " + test.get("data"));
+                        }
+                        Assert.assertEquals(0, errors.size());
+                    } else {
+                        if (errors.isEmpty()) {
+                            System.out.println("---- test case filed ----");
+                            System.out.println("schema: " + schema);
+                            System.out.println("data: " + test.get("data"));
+                        }
+                        Assert.assertEquals(false, errors.isEmpty());
+                    }
+                }
+            } catch (JsonSchemaException e) {
+                System.out.println("Bypass validation due to invalid schema: " + e.getMessage());
+            }
+        }
+    }
+
+    @Test(/*expected = java.lang.StackOverflowError.class*/)
+    public void testLoadingWithId() throws Exception {
+        URL url = new URL("http://localhost:1234/self_ref/selfRef.json");
+        JsonNode schemaJson = mapper.readTree(url);
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
+        @SuppressWarnings("unused")
+        JsonSchema schema = factory.getSchema(schemaJson);
+    }
+
+    @Test
+    public void testBignumValidator() throws Exception {
+        runTestFile("tests/optional/bignum.json");
+    }
+
+    @Test
+    public void testFormatValidator() throws Exception {
+        runTestFile("tests/optional/format.json");
+    }
+
+    @Test
+    public void testComplexSchema() throws Exception {
+        runTestFile("tests/optional/complex.json");
+    }
+
+    @Test
+    public void testZeroTerminatedFloatsValidator() throws Exception {
+        runTestFile("tests/optional/zeroTerminatedFloats.json");
+    }
+
+    @Test
+    public void testAdditionalItemsValidator() throws Exception {
+        runTestFile("tests/additionalItems.json");
+    }
+
+    @Test
+    public void testAdditionalPropertiesValidator() throws Exception {
+        runTestFile("tests/additionalProperties.json");
+    }
+
+    @Test
+    public void testAllOfValidator() throws Exception {
+        runTestFile("tests/allOf.json");
+    }
+
+    @Test
+    public void testAnyOFValidator() throws Exception {
+        runTestFile("tests/anyOf.json");
+    }
+
+    @Test
+    public void testDefaultValidator() throws Exception {
+        runTestFile("tests/default.json");
+    }
+
+    @Test
+    public void testDefinitionsValidator() throws Exception {
+        runTestFile("tests/definitions.json");
+    }
+
+    @Test
+    public void testDependenciesValidator() throws Exception {
+        runTestFile("tests/dependencies.json");
+    }
+
+    @Test
+    public void testEnumValidator() throws Exception {
+        runTestFile("tests/enum.json");
+    }
+
+    @Test
+    public void testItemsValidator() throws Exception {
+        runTestFile("tests/items.json");
+    }
+
+    @Test
+    public void testMaximumValidator() throws Exception {
+        runTestFile("tests/maximum.json");
+    }
+
+    @Test
+    public void testMaxItemsValidator() throws Exception {
+        runTestFile("tests/maxItems.json");
+    }
+
+    @Test
+    public void testMaxLengthValidator() throws Exception {
+        runTestFile("tests/maxLength.json");
+    }
+
+    @Test
+    public void testMaxPropertiesValidator() throws Exception {
+        runTestFile("tests/maxProperties.json");
+    }
+
+    @Test
+    public void testMinimumValidator() throws Exception {
+        runTestFile("tests/minimum.json");
+    }
+
+    @Test
+    public void testMinItemsValidator() throws Exception {
+        runTestFile("tests/minItems.json");
+    }
+
+    @Test
+    public void testMinLengthValidator() throws Exception {
+        runTestFile("tests/minLength.json");
+    }
+
+    @Test
+    public void testMinPropertiesValidator() throws Exception {
+        runTestFile("tests/minProperties.json");
+    }
+
+    @Test
+    public void testMultipleOfValidator() throws Exception {
+        runTestFile("tests/multipleOf.json");
+    }
+
+    @Test
+    public void testNotValidator() throws Exception {
+        runTestFile("tests/not.json");
+    }
+
+    @Test
+    public void testOneOfValidator() throws Exception {
+        runTestFile("tests/oneOf.json");
+    }
+    
+    @Test
+    public void testPatternValidator() throws Exception {
+        runTestFile("tests/pattern.json");
+    }
+
+    @Test
+    public void testPatternPropertiesValidator() throws Exception {
+        runTestFile("tests/patternProperties.json");
+    }
+
+    @Test
+    public void testPropertiesValidator() throws Exception {
+        runTestFile("tests/properties.json");
+    }
+
+    @Test
+    public void testRefValidator() throws Exception {
+        runTestFile("tests/ref.json");
+    }
+
+    @Test
+    public void testRefRemoteValidator() throws Exception {
+        runTestFile("tests/refRemote.json");
+    }
+
+    @Test
+    public void testRequiredValidator() throws Exception {
+        runTestFile("tests/required.json");
+    }
+
+    @Test
+    public void testTypeValidator() throws Exception {
+        runTestFile("tests/type.json");
+    }
+
+    @Test
+    public void testUnionTypeValidator() throws Exception {
+        runTestFile("tests/union_type.json");
+    }
+
+    @Test
+    public void testUniqueItemsValidator() throws Exception {
+        runTestFile("tests/uniqueItems.json");
+    }
+    
+    @Test
+    public void testIdSchemaWithUrl() throws Exception {
+        runTestFile("tests/id_schema/property.json");
+    }
+
+    @Test
+    public void testSchemaFromClasspath() throws Exception {
+        runTestFile("tests/classpath/schema.json");
+    }
+
+}
diff --git a/src/test/resources/tests/enum.json b/src/test/resources/tests/enum.json
index f124436..2b418a2 100644
--- a/src/test/resources/tests/enum.json
+++ b/src/test/resources/tests/enum.json
@@ -8,6 +8,18 @@
                 "data": 1,
                 "valid": true
             },
+            {
+                "description": "one of the enum is valid",
+                "isTypeLoose": true,
+                "data": "1",
+                "valid": true
+            },
+            {
+                "description": "something else is invalid",
+                "isTypeLoose": false,
+                "data": "1",
+                "valid": false
+            },
             {
                 "description": "something else is invalid",
                 "data": 4,
@@ -33,6 +45,18 @@
                 "description": "objects are deep compared",
                 "data": {"foo": false},
                 "valid": false
+            },
+            {
+                "description": "one of the enum is valid",
+                "data": "true",
+                "isTypeLoose": true,
+                "valid": true
+            },
+            {
+                "description": "something else is invalid",
+                "data": "true",
+                "isTypeLoose": false,
+                "valid": false
             }
         ]
     },
diff --git a/src/test/resources/tests/maximum.json b/src/test/resources/tests/maximum.json
index 86c7b89..214d126 100644
--- a/src/test/resources/tests/maximum.json
+++ b/src/test/resources/tests/maximum.json
@@ -8,6 +8,11 @@
                 "data": 2.6,
                 "valid": true
             },
+            {
+                "description": "below the maximum is valid",
+                "data": "2.6",
+                "valid": true
+            },
             {
                 "description": "above the maximum is invalid",
                 "data": 3.5,
diff --git a/src/test/resources/tests/minimum.json b/src/test/resources/tests/minimum.json
index d5bf000..fea55b8 100644
--- a/src/test/resources/tests/minimum.json
+++ b/src/test/resources/tests/minimum.json
@@ -8,6 +8,11 @@
                 "data": 2.6,
                 "valid": true
             },
+            {
+                "description": "above the minimum is valid",
+                "data": "2.6",
+                "valid": true
+            },
             {
                 "description": "below the minimum is invalid",
                 "data": 0.6,
diff --git a/src/test/resources/tests/optional/format.json b/src/test/resources/tests/optional/format.json
index 6ac59b6..34236d3 100644
--- a/src/test/resources/tests/optional/format.json
+++ b/src/test/resources/tests/optional/format.json
@@ -24,10 +24,30 @@
                 "valid": false
             },
             {
-                "description": "an invalid date-time string",
+                "description": "an valid date-time string",
                 "data": "1963-12-31T08:30:06.283185Z",
                 "valid": true
             },
+            {
+                "description": "an valid date-time string",
+                "data": "1963-12-30T08:30:06.283Z",
+                "valid": true
+            },
+            {
+                "description": "an valid date-time string",
+                "data": "1963-12-30T08:30:06.283",
+                "valid": true
+            },
+            {
+                "description": "an valid date-time string",
+                "data": "1963-12-30T08:30:06",
+                "valid": true
+            },
+            {
+                "description": "an valid date-time string",
+                "data": "1963-12-30T08:30:06Z",
+                "valid": true
+            },
             {
                 "description": "only RFC3339 not all of ISO 8601 are valid",
                 "data": "2013-350T01:01:01",