Repository: networknt/json-schema-validator
Pull request #133 (2019-04-08 21:15:24)

maximum/minimum validation of integral numbers
Addressing issue #132 

Test coverage added to elaborate on various cases. 

It turns out `jackson` differentiates between `int`, `long` and `BigInteger` out of the box. However difference between `double` and `BigDecimal` is an option turned off by default. Furthermore, when enabled `DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS` still parses value as `double` by default and later converts to `BigDecimal`. Thus values greater then `Double.MAX_VALUE` are interpreted as `Infinity`


Patch:
diff --git a/src/main/java/com/networknt/schema/MaximumValidator.java b/src/main/java/com/networknt/schema/MaximumValidator.java
index 01d701a..56b6b60 100644
--- a/src/main/java/com/networknt/schema/MaximumValidator.java
+++ b/src/main/java/com/networknt/schema/MaximumValidator.java
@@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.math.BigInteger;
 import java.util.Collections;
 import java.util.Set;
 
@@ -27,15 +28,15 @@ 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) {
+    private final ThresholdMixin typedMaximum;
+
 
+    public MaximumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
         super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAXIMUM, validationContext);
-        if (schemaNode.isNumber()) {
-            maximum = schemaNode.doubleValue();
-        } else {
+
+        if (!schemaNode.isNumber()) {
             throw new JsonSchemaException("maximum value is not a number");
         }
 
@@ -45,6 +46,56 @@ public class MaximumValidator extends BaseJsonValidator implements JsonValidator
         }
 
         parseErrorCode(getValidatorType().getErrorCodeKey());
+
+        if (!JsonType.INTEGER.toString().equals(getNodeFieldType())) {
+            // "number" or no type
+            // by default treat value as double: compatible with previous behavior
+            final double dm = schemaNode.doubleValue();
+            typedMaximum = new ThresholdMixin() {
+                @Override
+                public boolean crossesThreshold(JsonNode node) {
+                    double value = node.asDouble();
+                    return greaterThan(value, dm) || (excludeEqual && MaximumValidator.this.equals(value, dm));
+                }
+
+                @Override
+                public String thresholdValue() {
+                    return String.valueOf(dm);
+                }
+            };
+
+        } else if ( schemaNode.isLong() || schemaNode.isInt() ) {
+            // "integer", and within long range
+            final long lm = schemaNode.asLong();
+            typedMaximum = new ThresholdMixin() {
+                @Override
+                public boolean crossesThreshold(JsonNode node) {
+                    long val = node.asLong();
+                    return node.isBigInteger() || lm < val || (excludeEqual && lm <= val);
+                }
+
+                @Override
+                public String thresholdValue() {
+                    return String.valueOf(lm);
+                }
+            };
+
+        } else {
+            // "integer" outside long range
+            final BigInteger bim = new BigInteger(schemaNode.asText());
+            typedMaximum = new ThresholdMixin() {
+                @Override
+                public boolean crossesThreshold(JsonNode node) {
+                    int cmp = bim.compareTo(node.bigIntegerValue());
+                    return cmp < 0 || (excludeEqual && cmp <= 0);
+                }
+
+                @Override
+                public String thresholdValue() {
+                    return String.valueOf(bim);
+                }
+            };
+        }
     }
 
     public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
@@ -55,16 +106,10 @@ public class MaximumValidator extends BaseJsonValidator implements JsonValidator
             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));
+        if (typedMaximum.crossesThreshold(node)) {
+            return Collections.singleton(buildValidationMessage(at, typedMaximum.thresholdValue()));
         }
         return Collections.emptySet();
     }
 
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/MinimumValidator.java b/src/main/java/com/networknt/schema/MinimumValidator.java
index e9b0f24..4b1fe5c 100644
--- a/src/main/java/com/networknt/schema/MinimumValidator.java
+++ b/src/main/java/com/networknt/schema/MinimumValidator.java
@@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.math.BigInteger;
 import java.util.Collections;
 import java.util.Set;
 
@@ -27,14 +28,18 @@ 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;
 
+    /**
+     *  In order to limit number of `if` statements in `validate` method, all the
+     *  logic of picking the right comparison is abstracted into a mixin.
+     */
+    private final ThresholdMixin typedMinimum;
+
     public MinimumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
         super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MINIMUM, validationContext);
-        if (schemaNode.isNumber()) {
-            minimum = schemaNode.doubleValue();
-        } else {
+
+        if (!schemaNode.isNumber()) {
             throw new JsonSchemaException("minimum value is not a number");
         }
 
@@ -44,6 +49,56 @@ public class MinimumValidator extends BaseJsonValidator implements JsonValidator
         }
 
         parseErrorCode(getValidatorType().getErrorCodeKey());
+
+        if (!JsonType.INTEGER.toString().equals(getNodeFieldType())) {
+            // "number" or no type
+            // by default treat value as double: compatible with previous behavior
+            final double dmin = schemaNode.doubleValue();
+            typedMinimum = new ThresholdMixin() {
+                @Override
+                public boolean crossesThreshold(JsonNode node) {
+                    double value = node.asDouble();
+                    return lessThan(value, dmin) || (excluded && MinimumValidator.this.equals(value, dmin));
+                }
+
+                @Override
+                public String thresholdValue() {
+                    return String.valueOf(dmin);
+                }
+            };
+
+        } else if ( schemaNode.isLong() || schemaNode.isInt() ) {
+            // "integer", and within long range
+            final long lmin = schemaNode.asLong();
+            typedMinimum = new ThresholdMixin() {
+                @Override
+                public boolean crossesThreshold(JsonNode node) {
+                    long val = node.asLong();
+                    return node.isBigInteger() || lmin > val || (excluded && lmin >= val);
+                }
+
+                @Override
+                public String thresholdValue() {
+                    return String.valueOf(lmin);
+                }
+            };
+
+        } else {
+            // "integer" outside long range
+            final BigInteger bimin = new BigInteger(schemaNode.asText());
+            typedMinimum = new ThresholdMixin() {
+                @Override
+                public boolean crossesThreshold(JsonNode node) {
+                    int cmp = bimin.compareTo(node.bigIntegerValue());
+                    return cmp > 0 || (excluded && cmp >= 0);
+                }
+
+                @Override
+                public String thresholdValue() {
+                    return String.valueOf(bimin);
+                }
+            };
+        }
     }
 
     public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
@@ -53,14 +108,9 @@ public class MinimumValidator extends BaseJsonValidator implements JsonValidator
             // 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));
+
+        if (typedMinimum.crossesThreshold(node)) {
+            return Collections.singleton(buildValidationMessage(at, typedMinimum.thresholdValue()));
         }
         return Collections.emptySet();
     }
diff --git a/src/main/java/com/networknt/schema/ThresholdMixin.java b/src/main/java/com/networknt/schema/ThresholdMixin.java
new file mode 100644
index 0000000..ad8e1bd
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ThresholdMixin.java
@@ -0,0 +1,8 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public interface ThresholdMixin {
+    boolean crossesThreshold(JsonNode node);
+    String thresholdValue();
+}


Test patch:
diff --git a/src/test/java/com/networknt/schema/MaximumValidatorTest.java b/src/test/java/com/networknt/schema/MaximumValidatorTest.java
new file mode 100644
index 0000000..0107866
--- /dev/null
+++ b/src/test/java/com/networknt/schema/MaximumValidatorTest.java
@@ -0,0 +1,208 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Set;
+
+import static java.lang.String.format;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class MaximumValidatorTest {
+    private static JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
+
+    private static ObjectMapper mapper;
+    private static ObjectMapper bigDecimalMapper;
+
+    @Before
+    public void setUp() {
+        mapper = new ObjectMapper();
+        bigDecimalMapper = new ObjectMapper().enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
+    }
+
+    @Test
+    public void doubleValueOverflow() throws IOException {
+        String[][] values = {
+//            maximum,                           value
+            {"1.7976931348623157e+308",         "1.7976931348623159e+308"},
+            {"1.7976931348623156e+308",         "1.7976931348623157e+308"},
+//          See a {@link #doubleValueCoarsing() doubleValueCoarsing} test notes below
+//            {"1.7976931348623157e+308",         "1.7976931348623158e+308"},
+        };
+
+        for(String[] aTestCycle : values) {
+            String maximum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": %s }", maximum);
+
+            JsonSchema v = factory.getSchema(mapper.readTree(schema));
+            JsonNode doc = mapper.readTree(value);
+
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertFalse(format("Expecing validation error with maximum %s and value %s", maximum, value), messages.isEmpty());
+        }
+    }
+
+    @Test
+    public void documentParsedWithBigDecimal() throws IOException {
+        String[][] values = {
+//            maximum,                           value
+            {"1.7976931348623157e+308",         "1.7976931348623159e+308"},
+            {"1.7976931348623156e+308",         "1.7976931348623157e+308"},
+//          See a {@link #doubleValueCoarsing() doubleValueCoarsing} test notes below
+//            {"1.7976931348623157e+308",         "1.7976931348623158e+308"},
+        };
+
+        for(String[] aTestCycle : values) {
+            String maximum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": %s }", maximum);
+
+            JsonSchema v = factory.getSchema(mapper.readTree(schema));
+            JsonNode doc = bigDecimalMapper.readTree(value);
+
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertFalse(format("Expecing validation error with maximum %s and value %s", maximum, value), messages.isEmpty());
+        }
+    }
+
+    @Test
+    public void documentAndSchemaParsedWithBigDecimal() throws IOException {
+        String[][] values = {
+//            maximum,                           value
+            {"1.7976931348623157e+308",         "1.7976931348623159e+308"},
+            {"1.7976931348623156e+308",         "1.7976931348623157e+308"},
+//          See a {@link #doubleValueCoarsing() doubleValueCoarsing} test notes below
+//            {"1.7976931348623157e+308", "1.7976931348623158e+308"},
+        };
+
+        for(String[] aTestCycle : values) {
+            String maximum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": %s }", maximum);
+
+            JsonSchema v = factory.getSchema(bigDecimalMapper.readTree(schema));
+            JsonNode doc = bigDecimalMapper.readTree(value);
+
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertFalse(format("Expecing validation error with maximum %s and value %s", maximum, value), messages.isEmpty());
+        }
+    }
+
+    @Test
+    public void negativeDoubleOverflowTest() throws IOException {
+        String[][] values = {
+//            maximum,                           value
+            {"1.79769313486231571E+308",        "1.79769313486231572e+308"},
+            {"1.7976931348623157E+309",         "1.7976931348623157e+309"},
+            {"1.000000000000000000000001E+308", "1.000000000000000000000001E+308"},
+            {"1.000000000000000000000001E+400", "1.000000000000000000000001E+401"},
+            {"1.000000000000000000000001E+400", "1.000000000000000000000002E+400"},
+            {"1.000000000000000000000001E+400", "1.0000000000000000000000011E+400"}
+        };
+
+        for(String[] aTestCycle : values) {
+            String maximum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": %s }", maximum);
+
+            // Schema and document parsed with just double
+            JsonSchema v = factory.getSchema(mapper.readTree(schema));
+            JsonNode doc = mapper.readTree(value);
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertTrue(format("Maximum %s and value %s are interpreted as Infinity, thus no schema violation should be reported", maximum, value), messages.isEmpty());
+
+            // document parsed with BigDecimal
+            doc = bigDecimalMapper.readTree(value);
+            messages = v.validate(doc);
+            assertTrue(format("Maximum %s and value %s are interpreted as Infinity, thus no schema violation should be reported", maximum, value), messages.isEmpty());
+
+            // schema and document parsed with BigDecimal
+            v = factory.getSchema(bigDecimalMapper.readTree(schema));
+            messages = v.validate(doc);
+            assertTrue(format("Maximum %s and value %s are interpreted as Infinity, thus no schema violation should be reported", maximum, value), messages.isEmpty());
+        }
+    }
+
+    /**
+     *  value of 1.7976931348623158e+308 is not converted to POSITIVE_INFINITY for some reason
+     *  the only way to spot this is to use BigDecimal for schema (and for document)
+     */
+    @Test
+    public void doubleValueCoarsing() throws IOException {
+        String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": 1.7976931348623157e+308 }";
+        String content = "1.7976931348623158e+308";
+
+        JsonNode doc = mapper.readTree(content);
+        JsonSchema v = factory.getSchema(mapper.readTree(schema));
+
+        Set<ValidationMessage> messages = v.validate(doc);
+        assertTrue("Validation should succeed as by default double values are used by mapper", messages.isEmpty());
+
+        doc = bigDecimalMapper.readTree(content);
+        messages = v.validate(doc);
+        assertTrue("Validation should succeed as by default double values are used by mapper", messages.isEmpty());
+
+        /**
+         * Note: technically this is where 1.7976931348623158e+308 rounding to 1.7976931348623157e+308 could be spotted,
+         *       yet it requires a dedicated case of comparison BigDecimal to BigDecimal. Since values above
+         *       1.7976931348623158e+308 are parsed as Infinity anyways (jackson uses double as primary type with later
+         *       "upcasting" to BigDecimal, if property is set) adding a dedicated code block just for this one case
+         *       seems infeasible.
+         */
+        v = factory.getSchema(bigDecimalMapper.readTree(schema));
+        messages = v.validate(doc);
+        assertTrue("Validation should succeed as by default double values are used by mapper", messages.isEmpty());
+    }
+
+    @Test
+    public void longUnderMaxValueOverflow() throws IOException {
+        String[][] values = {
+//            maximum,                value
+            {"9223372036854775800",  "9223372036854775855"},
+            {"9223372036854775807",  "9223372036854775808"},
+            {"9223372036854775807",  new BigDecimal(String.valueOf(Double.MAX_VALUE)).add(BigDecimal.ONE).toString()},
+            {"9223372036854775806",  new BigDecimal(String.valueOf(Double.MAX_VALUE)).add(BigDecimal.ONE).toString()},
+            {"9223372036854776000",  "9223372036854776001"}
+        };
+        for(String[] aTestCycle : values) {
+            String maximum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"maximum\": %s }", maximum);
+
+            JsonSchema v = factory.getSchema(mapper.readTree(schema));
+            JsonNode doc = mapper.readTree(value);
+
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertFalse(format("Expecing validation error with maximum %s and value %s", maximum, value), messages.isEmpty());
+        }
+    }
+
+    @Test
+    public void longValueOverflowWithInverseEffect() throws IOException {
+        String[][] values = {
+//            maximum,                       value
+            {"9223372036854775000",         "9223372036854774988"}
+        };
+
+        for(String[] aTestCycle : values) {
+            String maximum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"maximum\": %s, \"exclusiveMaximum\": true}", maximum);
+
+            JsonSchema v = factory.getSchema(mapper.readTree(schema));
+            JsonNode doc = mapper.readTree(value);
+
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertTrue(format("Expecing no validation errors as maximum %s is greater than value %s", maximum, value), messages.isEmpty());
+        }
+    }
+}
+
+
diff --git a/src/test/java/com/networknt/schema/MinimumValidatorTest.java b/src/test/java/com/networknt/schema/MinimumValidatorTest.java
new file mode 100644
index 0000000..a2a53e9
--- /dev/null
+++ b/src/test/java/com/networknt/schema/MinimumValidatorTest.java
@@ -0,0 +1,208 @@
+package com.networknt.schema;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Set;
+
+import static java.lang.String.format;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class MinimumValidatorTest {
+    private static JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
+
+    private static ObjectMapper mapper;
+    private static ObjectMapper bigDecimalMapper;
+
+    @Before
+    public void setUp() {
+        mapper = new ObjectMapper();
+        bigDecimalMapper = new ObjectMapper().enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
+    }
+
+    @Test
+    public void doubleValueOverflow() throws IOException {
+        String[][] values = {
+//            minimum,                           value
+            {"-1.7976931348623157e+308",         "-1.7976931348623159e+308"},
+            {"-1.7976931348623156e+308",         "-1.7976931348623157e+308"},
+//          See a {@link #doubleValueCoarsing() doubleValueCoarsing} test notes below
+//            {"-1.7976931348623157e+308",         "-1.7976931348623158e+308"},
+        };
+
+        for(String[] aTestCycle : values) {
+            String minimum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": %s }", minimum);
+
+            JsonSchema v = factory.getSchema(mapper.readTree(schema));
+            JsonNode doc = mapper.readTree(value);
+
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertFalse(format("Expecing validation error with minimum %s and value %s", minimum, value), messages.isEmpty());
+        }
+    }
+
+    @Test
+    public void documentParsedWithBigDecimal() throws IOException {
+        String[][] values = {
+//            minimum,                           value
+            {"-1.7976931348623157e+308",         "-1.7976931348623159e+308"},
+            {"-1.7976931348623156e+308",         "-1.7976931348623157e+308"},
+//          See a {@link #doubleValueCoarsing() doubleValueCoarsing} test notes below
+//            {"-1.7976931348623157e+308",         "-1.7976931348623158e+308"},
+        };
+
+        for(String[] aTestCycle : values) {
+            String minimum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": %s }", minimum);
+
+            JsonSchema v = factory.getSchema(mapper.readTree(schema));
+            JsonNode doc = bigDecimalMapper.readTree(value);
+
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertFalse(format("Expecing validation error with minimum %s and value %s", minimum, value), messages.isEmpty());
+        }
+    }
+
+    @Test
+    public void documentAndSchemaParsedWithBigDecimal() throws IOException {
+        String[][] values = {
+//            minimum,                            value
+            {"-1.7976931348623157e+308",         "-1.7976931348623159e+308"},
+            {"-1.7976931348623156e+308",         "-1.7976931348623157e+308"},
+//          See a {@link #doubleValueCoarsing() doubleValueCoarsing} test notes below
+//            {"-1.7976931348623157e+308", "-1.7976931348623158e+308"},
+        };
+
+        for(String[] aTestCycle : values) {
+            String minimum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": %s }", minimum);
+
+            JsonSchema v = factory.getSchema(bigDecimalMapper.readTree(schema));
+            JsonNode doc = bigDecimalMapper.readTree(value);
+
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertFalse(format("Expecing validation error with minimum %s and value %s", minimum, value), messages.isEmpty());
+        }
+    }
+
+    @Test
+    public void negativeDoubleOverflowTest() throws IOException {
+        String[][] values = {
+//            minimum,                            value
+            {"-1.79769313486231571E+308",        "-1.79769313486231572e+308"},
+            {"-1.7976931348623157E+309",         "-1.7976931348623157e+309"},
+            {"-1.000000000000000000000001E+308", "-1.000000000000000000000001E+308"},
+            {"-1.000000000000000000000001E+400", "-1.000000000000000000000001E+401"},
+            {"-1.000000000000000000000001E+400", "-1.000000000000000000000002E+400"},
+            {"-1.000000000000000000000001E+400", "-1.0000000000000000000000011E+400"}
+        };
+
+        for(String[] aTestCycle : values) {
+            String minimum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": %s }", minimum);
+
+            // Schema and document parsed with just double
+            JsonSchema v = factory.getSchema(mapper.readTree(schema));
+            JsonNode doc = mapper.readTree(value);
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertTrue(format("Minimum %s and value %s are interpreted as Infinity, thus no schema violation should be reported", minimum, value), messages.isEmpty());
+
+            // document parsed with BigDecimal
+            doc = bigDecimalMapper.readTree(value);
+            messages = v.validate(doc);
+            assertTrue(format("Minimum %s and value %s are interpreted as Infinity, thus no schema violation should be reported", minimum, value), messages.isEmpty());
+
+            // schema and document parsed with BigDecimal
+            v = factory.getSchema(bigDecimalMapper.readTree(schema));
+            messages = v.validate(doc);
+            assertTrue(format("Minimum %s and value %s are interpreted as Infinity, thus no schema violation should be reported", minimum, value), messages.isEmpty());
+        }
+    }
+
+    /**
+     *  value of -1.7976931348623158e+308 is not converted to NEGATIVE_INFINITY for some reason
+     *  the only way to spot this is to use BigDecimal for schema (and for document)
+     */
+    @Test
+    public void doubleValueCoarsing() throws IOException {
+        String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": -1.7976931348623157e+308 }";
+        String content = "-1.7976931348623158e+308";
+
+        JsonNode doc = mapper.readTree(content);
+        JsonSchema v = factory.getSchema(mapper.readTree(schema));
+
+        Set<ValidationMessage> messages = v.validate(doc);
+        assertTrue("Validation should succeed as by default double values are used by mapper", messages.isEmpty());
+
+        doc = bigDecimalMapper.readTree(content);
+        messages = v.validate(doc);
+        assertTrue("Validation should succeed as by default double values are used by mapper", messages.isEmpty());
+
+        /**
+         * Note: technically this is where -1.7976931348623158e+308 rounding to -1.7976931348623157e+308 could be
+         *       spotted, yet it requires a dedicated case of comparison BigDecimal to BigDecimal. Since values below
+         *       -1.7976931348623158e+308 are parsed as Infinity anyways (jackson uses double as primary type with later
+         *       "upcasting" to BigDecimal, if property is set) adding a dedicated code block just for this one case
+         *       seems infeasible.
+         */
+        v = factory.getSchema(bigDecimalMapper.readTree(schema));
+        messages = v.validate(doc);
+        assertTrue("Validation should succeed as by default double values are used by mapper", messages.isEmpty());
+    }
+
+    @Test
+    public void longUnderMinValueOverflow() throws IOException {
+        String[][] values = {
+//            minimum,                value
+            {"-9223372036854775800",  "-9223372036854775855"},
+            {"-9223372036854775808",  "-9223372036854775809"},
+            {"-9223372036854775808",  new BigDecimal(String.valueOf(-Double.MAX_VALUE)).subtract(BigDecimal.ONE).toString()},
+            {"-9223372036854775807",  new BigDecimal(String.valueOf(-Double.MAX_VALUE)).subtract(BigDecimal.ONE).toString()},
+            {"-9223372036854776000",  "-9223372036854776001"}
+        };
+        for(String[] aTestCycle : values) {
+            String minimum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"minimum\": %s }", minimum);
+
+            JsonSchema v = factory.getSchema(mapper.readTree(schema));
+            JsonNode doc = mapper.readTree(value);
+
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertFalse(format("Expecing validation error with minimum %s and value %s", minimum, value), messages.isEmpty());
+        }
+    }
+
+    @Test
+    public void longValueOverflowWithInverseEffect() throws IOException {
+        String[][] values = {
+//            minimum,                       value
+            {"-9223372036854775000",         "-9223372036854774988"}
+        };
+
+        for(String[] aTestCycle : values) {
+            String minimum = aTestCycle[0];
+            String value = aTestCycle[1];
+            String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"minimum\": %s, \"exclusiveMinimum\": true}", minimum);
+
+            JsonSchema v = factory.getSchema(mapper.readTree(schema));
+            JsonNode doc = mapper.readTree(value);
+
+            Set<ValidationMessage> messages = v.validate(doc);
+            assertTrue(format("Expecing no validation errors as minimum %s is lesser than value %s", minimum, value), messages.isEmpty());
+        }
+    }
+}
+
+