Skip to content

Commit f31f698

Browse files
committed
-change minimum validator to use BigDecimal to do comparsion with similar logic to MaximumValidator
-added test cases
1 parent 40dc57e commit f31f698

File tree

3 files changed

+165
-28
lines changed

3 files changed

+165
-28
lines changed

src/main/java/com/networknt/schema/MinimumValidator.java

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.slf4j.Logger;
2121
import org.slf4j.LoggerFactory;
2222

23+
import java.math.BigDecimal;
2324
import java.math.BigInteger;
2425
import java.util.Collections;
2526
import java.util.Set;
@@ -50,24 +51,7 @@ public MinimumValidator(String schemaPath, JsonNode schemaNode, JsonSchema paren
5051

5152
parseErrorCode(getValidatorType().getErrorCodeKey());
5253

53-
if (!JsonType.INTEGER.toString().equals(getNodeFieldType())) {
54-
// "number" or no type
55-
// by default treat value as double: compatible with previous behavior
56-
final double dmin = schemaNode.doubleValue();
57-
typedMinimum = new ThresholdMixin() {
58-
@Override
59-
public boolean crossesThreshold(JsonNode node) {
60-
double value = node.asDouble();
61-
return lessThan(value, dmin) || (excluded && MinimumValidator.this.equals(value, dmin));
62-
}
63-
64-
@Override
65-
public String thresholdValue() {
66-
return String.valueOf(dmin);
67-
}
68-
};
69-
70-
} else if ( schemaNode.isLong() || schemaNode.isInt() ) {
54+
if ( schemaNode.isLong() || schemaNode.isInt() && JsonType.INTEGER.toString().equals(getNodeFieldType())) {
7155
// "integer", and within long range
7256
final long lmin = schemaNode.asLong();
7357
typedMinimum = new ThresholdMixin() {
@@ -90,18 +74,21 @@ public String thresholdValue() {
9074
};
9175

9276
} else {
93-
// "integer" outside long range
94-
final BigInteger bimin = new BigInteger(schemaNode.asText());
9577
typedMinimum = new ThresholdMixin() {
9678
@Override
9779
public boolean crossesThreshold(JsonNode node) {
98-
int cmp = bimin.compareTo(node.bigIntegerValue());
99-
return cmp > 0 || (excluded && cmp >= 0);
80+
if(schemaNode.doubleValue() == Double.NEGATIVE_INFINITY) {
81+
return false;
82+
}
83+
final BigDecimal min = new BigDecimal(schemaNode.asText());
84+
if(node.doubleValue() == Double.NEGATIVE_INFINITY) {return true;}
85+
BigDecimal value = new BigDecimal(node.asText());
86+
return value.compareTo(min) < 0 || (excluded && value.compareTo(min) == 0);
10087
}
10188

10289
@Override
10390
public String thresholdValue() {
104-
return String.valueOf(bimin);
91+
return schemaNode.asText();
10592
}
10693
};
10794
}

src/test/java/com/networknt/schema/MaximumValidatorTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,38 @@ public void doubleValueCoarsing() throws IOException {
194194
assertFalse("Validation should succeed as by default double values are used by mapper", messages.isEmpty());
195195
}
196196

197+
/**
198+
* BigDecimalMapper issue, it doesn't work as expected, it will treat 1.7976931348623159e+308 as INFINITY instead of as it is.
199+
*/
200+
@Test
201+
public void doubleValueCoarsingExceedRange() throws IOException {
202+
String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": 1.7976931348623159e+308 }";
203+
String content = "1.7976931348623160e+308";
204+
205+
JsonNode doc = mapper.readTree(content);
206+
JsonSchema v = factory.getSchema(mapper.readTree(schema));
207+
208+
Set<ValidationMessage> messages = v.validate(doc);
209+
assertTrue("Validation should succeed as by default double values are used by mapper", messages.isEmpty());
210+
211+
doc = bigDecimalMapper.readTree(content);
212+
messages = v.validate(doc);
213+
// "1.7976931348623158e+308" == "1.7976931348623157e+308" == Double.MAX_VALUE
214+
// new BigDecimal("1.7976931348623158e+308").compareTo(new BigDecimal("1.7976931348623157e+308")) > 0
215+
assertTrue("Validation should success because the bug of bigDecimalMapper, it will treat 1.7976931348623159e+308 as INFINITY", messages.isEmpty());
216+
217+
/**
218+
* Note: technically this is where 1.7976931348623158e+308 rounding to 1.7976931348623157e+308 could be spotted,
219+
* yet it requires a dedicated case of comparison BigDecimal to BigDecimal. Since values above
220+
* 1.7976931348623158e+308 are parsed as Infinity anyways (jackson uses double as primary type with later
221+
* "upcasting" to BigDecimal, if property is set) adding a dedicated code block just for this one case
222+
* seems infeasible.
223+
*/
224+
v = factory.getSchema(bigDecimalMapper.readTree(schema));
225+
messages = v.validate(doc);
226+
assertTrue("Validation should success because the bug of bigDecimalMapper, it will treat 1.7976931348623159e+308 as INFINITY", messages.isEmpty());
227+
}
228+
197229
@Test
198230
public void longUnderMaxValueOverflow() throws IOException {
199231
String[][] values = {

src/test/java/com/networknt/schema/MinimumValidatorTest.java

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,28 @@ public void negativeDoubleOverflowTest() throws IOException {
139139

140140
// document parsed with BigDecimal
141141
doc = bigDecimalMapper.readTree(value);
142-
messages = v.validate(doc);
143-
assertTrue(format("Minimum %s and value %s are interpreted as Infinity, thus no schema violation should be reported", minimum, value), messages.isEmpty());
142+
Set<ValidationMessage> messages2 = v.validate(doc);
143+
144+
//when the schema and value are both using BigDecimal, the value should be parsed in same mechanism.
145+
if(Double.valueOf(minimum) == Double.NEGATIVE_INFINITY) {
146+
/**
147+
* {"-1.000000000000000000000001E+308", "-1.000000000000000000000001E+308"} will be false
148+
* because the different between two mappers, without using big decimal, it loses some precises.
149+
*/
150+
assertTrue(format("Minimum %s and value %s are equal, thus no schema violation should be reported", minimum, value), messages2.isEmpty());
151+
} else {
152+
assertFalse(format("Minimum %s is larger than value %s , should be validation error reported", minimum, value), messages2.isEmpty());
153+
}
144154

145155
// schema and document parsed with BigDecimal
146156
v = factory.getSchema(bigDecimalMapper.readTree(schema));
147-
messages = v.validate(doc);
148-
assertTrue(format("Minimum %s and value %s are interpreted as Infinity, thus no schema violation should be reported", minimum, value), messages.isEmpty());
157+
Set<ValidationMessage> messages3 = v.validate(doc);
158+
//when the schema and value are both using BigDecimal, the value should be parsed in same mechanism.
159+
if(minimum.toLowerCase().equals(value.toLowerCase()) || Double.valueOf(minimum) == Double.NEGATIVE_INFINITY) {
160+
assertTrue(format("Minimum %s and value %s are equal, thus no schema violation should be reported", minimum, value), messages3.isEmpty());
161+
} else {
162+
assertFalse(format("Minimum %s is larger than value %s , should be validation error reported", minimum, value), messages3.isEmpty());
163+
}
149164
}
150165
}
151166

@@ -166,7 +181,7 @@ public void doubleValueCoarsing() throws IOException {
166181

167182
doc = bigDecimalMapper.readTree(content);
168183
messages = v.validate(doc);
169-
assertTrue("Validation should succeed as by default double values are used by mapper", messages.isEmpty());
184+
assertFalse("Validation should not succeed because content is using bigDecimalMapper, and smaller than the minimum", messages.isEmpty());
170185

171186
/**
172187
* Note: technically this is where -1.7976931348623158e+308 rounding to -1.7976931348623157e+308 could be
@@ -177,7 +192,30 @@ public void doubleValueCoarsing() throws IOException {
177192
*/
178193
v = factory.getSchema(bigDecimalMapper.readTree(schema));
179194
messages = v.validate(doc);
195+
assertFalse("Validation should not succeed because content is using bigDecimalMapper, and smaller than the minimum", messages.isEmpty());
196+
}
197+
198+
/**
199+
* BigDecimalMapper issue, it doesn't work as expected, it will treat -1.7976931348623157e+309 as INFINITY instead of as it is.
200+
*/
201+
@Test
202+
public void doubleValueCoarsingExceedRange() throws IOException {
203+
String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": -1.7976931348623159e+308 }";
204+
String content = "-1.7976931348623160e+308";
205+
206+
JsonNode doc = mapper.readTree(content);
207+
JsonSchema v = factory.getSchema(mapper.readTree(schema));
208+
209+
Set<ValidationMessage> messages = v.validate(doc);
180210
assertTrue("Validation should succeed as by default double values are used by mapper", messages.isEmpty());
211+
212+
doc = bigDecimalMapper.readTree(content);
213+
messages = v.validate(doc);
214+
assertTrue("Validation should succeed due to the bug of BigDecimal option of mapper", messages.isEmpty());
215+
216+
v = factory.getSchema(bigDecimalMapper.readTree(schema));
217+
messages = v.validate(doc);
218+
assertTrue("Validation should succeed due to the bug of BigDecimal option of mapper", messages.isEmpty());
181219
}
182220

183221
@Test
@@ -381,6 +419,86 @@ public void BigIntegerOverflowOnLongRangeEdge() throws IOException {
381419
assertTrue(format("Expecing no validation errors as maximum %s is greater than value %s", maximum, value), messages.isEmpty());
382420
}
383421
}
422+
423+
@Test
424+
public void testMinimumDoubleValue() throws IOException {
425+
String[][] values = {
426+
// minimum, value
427+
{"-1E309", "-1000"}
428+
};
429+
430+
for(String[] aTestCycle : values) {
431+
String minimum = aTestCycle[0];
432+
String value = aTestCycle[1];
433+
String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"minimum\": %s, \"exclusiveMinimum\": false}", minimum);
434+
435+
JsonSchema v = factory.getSchema(mapper.readTree(schema));
436+
JsonNode doc = mapper.readTree(value);
437+
438+
Set<ValidationMessage> messages = v.validate(doc);
439+
assertTrue(format("Expecting no validation errors as value %s is greater than minimum %s", value, minimum), messages.isEmpty());
440+
}
441+
}
442+
443+
@Test
444+
public void testMinimumDoubleValueNegative() throws IOException {
445+
String[][] values = {
446+
// minimum, value
447+
{"-1000", "-1E309"}
448+
};
449+
450+
for(String[] aTestCycle : values) {
451+
String minimum = aTestCycle[0];
452+
String value = aTestCycle[1];
453+
String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"minimum\": %s, \"exclusiveMinimum\": false}", minimum);
454+
455+
JsonSchema v = factory.getSchema(mapper.readTree(schema));
456+
JsonNode doc = mapper.readTree(value);
457+
458+
Set<ValidationMessage> messages = v.validate(doc);
459+
assertFalse(format("Expecting validation errors as value %s is smaller than minimum %s", value, minimum), messages.isEmpty());
460+
}
461+
}
462+
463+
@Test
464+
public void testMinimumDoubleValueWithNumberType() throws IOException {
465+
String[][] values = {
466+
// minimum, value
467+
{"1000", "1000.1"}
468+
};
469+
470+
for(String[] aTestCycle : values) {
471+
String minimum = aTestCycle[0];
472+
String value = aTestCycle[1];
473+
String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": %s, \"exclusiveMinimum\": false}", minimum);
474+
475+
JsonSchema v = factory.getSchema(mapper.readTree(schema));
476+
JsonNode doc = mapper.readTree(value);
477+
478+
Set<ValidationMessage> messages = v.validate(doc);
479+
assertTrue(format("Expecting no validation errors as value %s is greater than minimum %s", value, minimum), messages.isEmpty());
480+
}
481+
}
482+
483+
@Test
484+
public void testMinimumDoubleValueWithNumberTypeNegative() throws IOException {
485+
String[][] values = {
486+
// minimum, value
487+
{"1000.1", "1000"}
488+
};
489+
490+
for(String[] aTestCycle : values) {
491+
String minimum = aTestCycle[0];
492+
String value = aTestCycle[1];
493+
String schema = format("{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": %s, \"exclusiveMinimum\": false}", minimum);
494+
495+
JsonSchema v = factory.getSchema(mapper.readTree(schema));
496+
JsonNode doc = mapper.readTree(value);
497+
498+
Set<ValidationMessage> messages = v.validate(doc);
499+
assertFalse(format("Expecting validation errors as value %s is smaller than minimum %s", value, minimum), messages.isEmpty());
500+
}
501+
}
384502
}
385503

386504

0 commit comments

Comments
 (0)