Skip to content

Commit c0f288a

Browse files
authored
Merge pull request #213 from networknt/issue54
fixes #54 support for draft V6, V7 and V2019-09
2 parents a34af03 + 08ca9e4 commit c0f288a

File tree

182 files changed

+20187
-116
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

182 files changed

+20187
-116
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ For the latest version, please check the [release](https://github.com/networknt/
9292

9393
## [Configuration](doc/config.md)
9494

95+
## [Specification Version](doc/specversion.md)
96+
9597
## Known issues
9698

9799
I have just updated the test suites from the [official website](https://github.com/json-schema-org/JSON-Schema-Test-Suite) as the old ones were copied from another Java validator. Now there are several issues that need to be addressed. All of them are edge cases, in my opinion, but need to be investigated. As my old test suites were inherited from another Java JSON Schema Validator, I guess other Java Validator would have the same issues as these issues are in the Java language itself.

doc/specversion.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
The library supports V4, V6, V7, and V2019-09 JSON schema specifications. By default, V4 is used for backward compatibility.
2+
3+
### For Users
4+
5+
To create a draft V4 JsonSchemaFactory
6+
7+
```
8+
protected ObjectMapper mapper = new ObjectMapper();
9+
protected JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance()).objectMapper(mapper).build();
10+
```
11+
12+
The above code is exactly the same as before. Internally, it will default to the SpecVersion.VersionFlag.V4 as the parameter.
13+
14+
To create a draft V6 JsonSchemaFactory
15+
16+
```
17+
protected ObjectMapper mapper = new ObjectMapper();
18+
protected JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V6)).objectMapper(mapper).build();
19+
20+
```
21+
22+
To create a draft V7 JsonSchemaFactory
23+
24+
```
25+
protected ObjectMapper mapper = new ObjectMapper();
26+
protected JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)).objectMapper(mapper).build();
27+
```
28+
29+
To create a draft 2019-09 JsonSchemaFactory
30+
31+
```
32+
protected ObjectMapper mapper = new ObjectMapper();
33+
protected JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).objectMapper(mapper).build();
34+
```
35+
36+
### For Developers
37+
38+
#### SpecVersion
39+
40+
A new class SpecVersion has been introduced to indicate which version of the specification is used when creating the JsonSchemaFactory. The SpecVersion has an enum and two methods to convert a long to an EnumSet or a set of VersionFlags to a long value.
41+
42+
```
43+
public enum VersionFlag {
44+
45+
V4(1<<0),
46+
V6(1<<1),
47+
V7(1<<2),
48+
V201909(1<<3);
49+
50+
```
51+
52+
In the long value, we are using 4 bits now as we are supporting 4 versions at the moment.
53+
54+
V4 -> 0001 -> 1
55+
V6 -> 0010 -> 2
56+
V7 -> 0100 -> 4
57+
V201909 -> 1000 -> 8
58+
59+
If we have a new version added, it should be
60+
61+
V202009 -> 10000 -> 16
62+
63+
#### ValidatorTypeCode
64+
65+
A new field versionCode is added to indicate which version the validator is supported.
66+
67+
For most of the validators, the version code should be 15, which is 1111. This means the validator will be loaded for every version of the specification.
68+
69+
For example.
70+
71+
```
72+
MAXIMUM("maximum", "1011", new MessageFormat("{0}: must have a maximum value of {1}"), MaximumValidator.class, 15),
73+
```
74+
75+
Since if-then-else was introduced in the V7, it only works for V7 and V2019-09
76+
77+
```
78+
IF_THEN_ELSE("if", "1037", null, IfValidator.class, 12), // V7|V201909 1100
79+
```
80+
81+
For exclusiveMaximum, it was introduced from V6
82+
83+
```
84+
EXCLUSIVE_MAXIMUM("exclusiveMaximum", "1038", new MessageFormat("{0}: must have a exclusive maximum value of {1}"), ExclusiveMaximumValidator.class, 14), // V6|V7|V201909
85+
```
86+
87+
The getNonFormatKeywords method is updated to accept a SpecVersion.VersionFlag so that only the keywords supported by the specification will be loaded.
88+
89+
```
90+
public static List<ValidatorTypeCode> getNonFormatKeywords(SpecVersion.VersionFlag versionFlag) {
91+
final List<ValidatorTypeCode> result = new ArrayList<ValidatorTypeCode>();
92+
for (ValidatorTypeCode keyword: values()) {
93+
if (!FORMAT.equals(keyword) && specVersion.getVersionFlags(keyword.versionCode).contains(versionFlag)) {
94+
result.add(keyword);
95+
}
96+
}
97+
return result;
98+
}
99+
```
100+
101+
### JsonMetaSchema
102+
103+
We have created four different static classes V4, V6, V7, and V201909 to build different JsonMetaSchema instances.
104+
105+
For the BUILDIN_FORMATS, there is a common section, and each static class has its version-specific BUILDIN_FORMATS section.
106+
107+
108+
### JsonSchemaFactory
109+
110+
The getInstance supports a parameter SpecVersion.VersionFlag to get the right instance of the JsonMetaShema to create the factory. If there is no parameter, then V4 is used by default.
111+
112+
```
113+
public static JsonSchemaFactory getInstance() {
114+
return getInstance(SpecVersion.VersionFlag.V4);
115+
}
116+
117+
public static JsonSchemaFactory getInstance(SpecVersion.VersionFlag versionFlag) {
118+
if(versionFlag == SpecVersion.VersionFlag.V201909) {
119+
JsonMetaSchema v201909 = JsonMetaSchema.getV201909();
120+
return builder()
121+
.defaultMetaSchemaURI(v201909.getUri())
122+
.addMetaSchema(v201909)
123+
.build();
124+
} else if(versionFlag == SpecVersion.VersionFlag.V7) {
125+
JsonMetaSchema v7 = JsonMetaSchema.getV7();
126+
return builder()
127+
.defaultMetaSchemaURI(v7.getUri())
128+
.addMetaSchema(v7)
129+
.build();
130+
} else if(versionFlag == SpecVersion.VersionFlag.V6) {
131+
JsonMetaSchema v6 = JsonMetaSchema.getV6();
132+
return builder()
133+
.defaultMetaSchemaURI(v6.getUri())
134+
.addMetaSchema(v6)
135+
.build();
136+
} else if(versionFlag == SpecVersion.VersionFlag.V4) {
137+
JsonMetaSchema v4 = JsonMetaSchema.getV4();
138+
return builder()
139+
.defaultMetaSchemaURI(v4.getUri())
140+
.addMetaSchema(v4)
141+
.build();
142+
}
143+
return null;
144+
}
145+
146+
```
147+
148+
### For Testers
149+
150+
In the test resource folder, we have created and copied all draft version's test suite. They are located in draft4, draft6, draft7, and draft2019-09 folder.
151+
152+
The existing JsonSchemaTest has been renamed to V4JsonSchemaTest, and the following test classes are added.
153+
154+
```
155+
V6JsonSchemaTest
156+
V7JsonSchemaTest
157+
V201909JsonSchemaTest
158+
```
159+
160+
These new test classes are not completed yet, and only some sample test cases are added.
161+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.networknt.schema;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
7+
import java.math.BigDecimal;
8+
import java.util.Collections;
9+
import java.util.LinkedHashSet;
10+
import java.util.Set;
11+
12+
public class ConstValidator extends BaseJsonValidator implements JsonValidator {
13+
private static final Logger logger = LoggerFactory.getLogger(ConstValidator.class);
14+
JsonNode schemaNode;
15+
16+
public ConstValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
17+
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.CONST, validationContext);
18+
this.schemaNode = schemaNode;
19+
}
20+
21+
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
22+
debug(logger, node, rootNode, at);
23+
24+
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
25+
if(schemaNode.isNumber() && node.isNumber()) {
26+
if(schemaNode.decimalValue().compareTo(node.decimalValue()) != 0) {
27+
errors.add(buildValidationMessage(at, schemaNode.asText()));
28+
}
29+
} else if (!schemaNode.equals(node)) {
30+
errors.add(buildValidationMessage(at, schemaNode.asText()));
31+
}
32+
return Collections.unmodifiableSet(errors);
33+
}
34+
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.networknt.schema;
1818

1919
import com.fasterxml.jackson.databind.JsonNode;
20+
import com.fasterxml.jackson.databind.node.DecimalNode;
2021
import com.fasterxml.jackson.databind.node.NullNode;
2122

2223
import org.slf4j.Logger;
@@ -44,7 +45,12 @@ public EnumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSc
4445
String separator = "";
4546

4647
for (JsonNode n : schemaNode) {
47-
nodes.add(n);
48+
if(n.isNumber()) {
49+
// convert to DecimalNode for number comparison
50+
nodes.add(DecimalNode.valueOf(n.decimalValue()));
51+
} else {
52+
nodes.add(n);
53+
}
4854

4955
sb.append(separator);
5056
sb.append(n.asText());
@@ -77,7 +83,7 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
7783
debug(logger, node, rootNode, at);
7884

7985
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
80-
86+
if(node.isNumber()) node = DecimalNode.valueOf(node.decimalValue());
8187
if (!nodes.contains(node) && !(config.isTypeLoose() && isTypeLooseContainsInEnum(node))) {
8288
errors.add(buildValidationMessage(at, error));
8389
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright (c) 2016 Network New Technologies Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.networknt.schema;
18+
19+
import com.fasterxml.jackson.databind.JsonNode;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
import java.math.BigDecimal;
24+
import java.math.BigInteger;
25+
import java.util.Collections;
26+
import java.util.Set;
27+
28+
public class ExclusiveMaximumValidator extends BaseJsonValidator implements JsonValidator {
29+
private static final Logger logger = LoggerFactory.getLogger(ExclusiveMaximumValidator.class);
30+
31+
private final ThresholdMixin typedMaximum;
32+
33+
public ExclusiveMaximumValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
34+
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.EXCLUSIVE_MAXIMUM, validationContext);
35+
36+
if (!schemaNode.isNumber()) {
37+
throw new JsonSchemaException("exclusiveMaximum value is not a number");
38+
}
39+
40+
parseErrorCode(getValidatorType().getErrorCodeKey());
41+
42+
final String maximumText = schemaNode.asText();
43+
if (( schemaNode.isLong() || schemaNode.isInt() ) && (JsonType.INTEGER.toString().equals(getNodeFieldType()))) {
44+
// "integer", and within long range
45+
final long lm = schemaNode.asLong();
46+
typedMaximum = new ThresholdMixin() {
47+
@Override
48+
public boolean crossesThreshold(JsonNode node) {
49+
if (node.isBigInteger()) {
50+
//node.isBigInteger is not trustable, the type BigInteger doesn't mean it is a big number.
51+
int compare = node.bigIntegerValue().compareTo(new BigInteger(schemaNode.asText()));
52+
return compare > 0 || compare == 0;
53+
54+
} else if (node.isTextual()) {
55+
BigDecimal max = new BigDecimal(maximumText);
56+
BigDecimal value = new BigDecimal(node.asText());
57+
int compare = value.compareTo(max);
58+
return compare > 0 || compare == 0;
59+
}
60+
long val = node.asLong();
61+
return lm < val || lm == val;
62+
}
63+
64+
@Override
65+
public String thresholdValue() {
66+
return String.valueOf(lm);
67+
}
68+
};
69+
} else {
70+
typedMaximum = new ThresholdMixin() {
71+
@Override
72+
public boolean crossesThreshold(JsonNode node) {
73+
if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.POSITIVE_INFINITY) {
74+
return false;
75+
}
76+
if (schemaNode.isDouble() && schemaNode.doubleValue() == Double.NEGATIVE_INFINITY) {
77+
return true;
78+
}
79+
if (node.isDouble() && node.doubleValue() == Double.NEGATIVE_INFINITY) {
80+
return false;
81+
}
82+
if (node.isDouble() && node.doubleValue() == Double.POSITIVE_INFINITY) {
83+
return true;
84+
}
85+
final BigDecimal max = new BigDecimal(maximumText);
86+
BigDecimal value = new BigDecimal(node.asText());
87+
int compare = value.compareTo(max);
88+
return compare > 0 || compare == 0;
89+
}
90+
91+
@Override
92+
public String thresholdValue() {
93+
return maximumText;
94+
}
95+
};
96+
}
97+
}
98+
99+
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
100+
debug(logger, node, rootNode, at);
101+
102+
if (!TypeValidator.isNumber(node, config.isTypeLoose())) {
103+
// maximum only applies to numbers
104+
return Collections.emptySet();
105+
}
106+
107+
if (typedMaximum.crossesThreshold(node)) {
108+
return Collections.singleton(buildValidationMessage(at, typedMaximum.thresholdValue()));
109+
}
110+
return Collections.emptySet();
111+
}
112+
}

0 commit comments

Comments
 (0)