Skip to content

Commit 961182a

Browse files
authored
Merge pull request #133 from kosty/feature/ISSUE-132-kosty
maximum/minimum validation of integral numbers
2 parents 22fdad0 + 7a2e592 commit 961182a

File tree

5 files changed

+545
-26
lines changed

5 files changed

+545
-26
lines changed

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

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

23+
import java.math.BigInteger;
2324
import java.util.Collections;
2425
import java.util.Set;
2526

2627
public class MaximumValidator extends BaseJsonValidator implements JsonValidator {
2728
private static final Logger logger = LoggerFactory.getLogger(MaximumValidator.class);
2829
private static final String PROPERTY_EXCLUSIVE_MAXIMUM = "exclusiveMaximum";
2930

30-
private double maximum;
3131
private boolean excludeEqual = false;
3232

33-
public MaximumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
33+
private final ThresholdMixin typedMaximum;
34+
3435

36+
public MaximumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
3537
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAXIMUM, validationContext);
36-
if (schemaNode.isNumber()) {
37-
maximum = schemaNode.doubleValue();
38-
} else {
38+
39+
if (!schemaNode.isNumber()) {
3940
throw new JsonSchemaException("maximum value is not a number");
4041
}
4142

@@ -45,6 +46,56 @@ public MaximumValidator(String schemaPath, JsonNode schemaNode, JsonSchema paren
4546
}
4647

4748
parseErrorCode(getValidatorType().getErrorCodeKey());
49+
50+
if (!JsonType.INTEGER.toString().equals(getNodeFieldType())) {
51+
// "number" or no type
52+
// by default treat value as double: compatible with previous behavior
53+
final double dm = schemaNode.doubleValue();
54+
typedMaximum = new ThresholdMixin() {
55+
@Override
56+
public boolean crossesThreshold(JsonNode node) {
57+
double value = node.asDouble();
58+
return greaterThan(value, dm) || (excludeEqual && MaximumValidator.this.equals(value, dm));
59+
}
60+
61+
@Override
62+
public String thresholdValue() {
63+
return String.valueOf(dm);
64+
}
65+
};
66+
67+
} else if ( schemaNode.isLong() || schemaNode.isInt() ) {
68+
// "integer", and within long range
69+
final long lm = schemaNode.asLong();
70+
typedMaximum = new ThresholdMixin() {
71+
@Override
72+
public boolean crossesThreshold(JsonNode node) {
73+
long val = node.asLong();
74+
return node.isBigInteger() || lm < val || (excludeEqual && lm <= val);
75+
}
76+
77+
@Override
78+
public String thresholdValue() {
79+
return String.valueOf(lm);
80+
}
81+
};
82+
83+
} else {
84+
// "integer" outside long range
85+
final BigInteger bim = new BigInteger(schemaNode.asText());
86+
typedMaximum = new ThresholdMixin() {
87+
@Override
88+
public boolean crossesThreshold(JsonNode node) {
89+
int cmp = bim.compareTo(node.bigIntegerValue());
90+
return cmp < 0 || (excludeEqual && cmp <= 0);
91+
}
92+
93+
@Override
94+
public String thresholdValue() {
95+
return String.valueOf(bim);
96+
}
97+
};
98+
}
4899
}
49100

50101
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
@@ -55,16 +106,10 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
55106
return Collections.emptySet();
56107
}
57108

58-
String fieldType = this.getNodeFieldType();
59-
60-
double value = node.asDouble();
61-
if (greaterThan(value, maximum) || (excludeEqual && equals(value, maximum))) {
62-
if (JsonType.INTEGER.toString().equals(fieldType)) {
63-
return Collections.singleton(buildValidationMessage(at, "" + (int)maximum));
64-
}
65-
return Collections.singleton(buildValidationMessage(at, "" + maximum));
109+
if (typedMaximum.crossesThreshold(node)) {
110+
return Collections.singleton(buildValidationMessage(at, typedMaximum.thresholdValue()));
66111
}
67112
return Collections.emptySet();
68113
}
69114

70-
}
115+
}

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

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,26 @@
2020
import org.slf4j.Logger;
2121
import org.slf4j.LoggerFactory;
2222

23+
import java.math.BigInteger;
2324
import java.util.Collections;
2425
import java.util.Set;
2526

2627
public class MinimumValidator extends BaseJsonValidator implements JsonValidator {
2728
private static final Logger logger = LoggerFactory.getLogger(MinimumValidator.class);
2829
private static final String PROPERTY_EXCLUSIVE_MINIMUM = "exclusiveMinimum";
2930

30-
private double minimum;
3131
private boolean excluded = false;
3232

33+
/**
34+
* In order to limit number of `if` statements in `validate` method, all the
35+
* logic of picking the right comparison is abstracted into a mixin.
36+
*/
37+
private final ThresholdMixin typedMinimum;
38+
3339
public MinimumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
3440
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MINIMUM, validationContext);
35-
if (schemaNode.isNumber()) {
36-
minimum = schemaNode.doubleValue();
37-
} else {
41+
42+
if (!schemaNode.isNumber()) {
3843
throw new JsonSchemaException("minimum value is not a number");
3944
}
4045

@@ -44,6 +49,56 @@ public MinimumValidator(String schemaPath, JsonNode schemaNode, JsonSchema paren
4449
}
4550

4651
parseErrorCode(getValidatorType().getErrorCodeKey());
52+
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() ) {
71+
// "integer", and within long range
72+
final long lmin = schemaNode.asLong();
73+
typedMinimum = new ThresholdMixin() {
74+
@Override
75+
public boolean crossesThreshold(JsonNode node) {
76+
long val = node.asLong();
77+
return node.isBigInteger() || lmin > val || (excluded && lmin >= val);
78+
}
79+
80+
@Override
81+
public String thresholdValue() {
82+
return String.valueOf(lmin);
83+
}
84+
};
85+
86+
} else {
87+
// "integer" outside long range
88+
final BigInteger bimin = new BigInteger(schemaNode.asText());
89+
typedMinimum = new ThresholdMixin() {
90+
@Override
91+
public boolean crossesThreshold(JsonNode node) {
92+
int cmp = bimin.compareTo(node.bigIntegerValue());
93+
return cmp > 0 || (excluded && cmp >= 0);
94+
}
95+
96+
@Override
97+
public String thresholdValue() {
98+
return String.valueOf(bimin);
99+
}
100+
};
101+
}
47102
}
48103

49104
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
@@ -53,14 +108,9 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
53108
// minimum only applies to numbers
54109
return Collections.emptySet();
55110
}
56-
String fieldType = this.getNodeFieldType();
57-
58-
double value = node.asDouble();
59-
if (lessThan(value, minimum) || (excluded && equals(value, minimum))) {
60-
if (JsonType.INTEGER.toString().equals(fieldType)) {
61-
return Collections.singleton(buildValidationMessage(at, "" + (int) minimum));
62-
}
63-
return Collections.singleton(buildValidationMessage(at, "" + minimum));
111+
112+
if (typedMinimum.crossesThreshold(node)) {
113+
return Collections.singleton(buildValidationMessage(at, typedMinimum.thresholdValue()));
64114
}
65115
return Collections.emptySet();
66116
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.networknt.schema;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
5+
public interface ThresholdMixin {
6+
boolean crossesThreshold(JsonNode node);
7+
String thresholdValue();
8+
}

0 commit comments

Comments
 (0)