Skip to content

Commit 308e258

Browse files
authored
Issue425 (#432)
* Issue428: Handle nullable for oneOf and the child node * Issue428: Fix the duplicate validation for the oneOf type * issue425: Skip the validation when the test node is oneOf the defined Type and validate the node with the right schemaType only
1 parent 36095ff commit 308e258

File tree

6 files changed

+403
-8
lines changed

6 files changed

+403
-8
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
165165

166166
// get the current validator
167167
JsonSchema schema = validator.schema;
168+
169+
//Skip the validation when the current node is oneOf type and it is not equal to schemaType.
170+
if(JsonNodeUtil.matchOneOfTypeNode(schemaNode,TypeFactory.getValueNodeType(node, super.config)) && !JsonNodeUtil.equalsToSchemaType(node,schema,config) && !(JsonType.UNKNOWN.equals(JsonNodeUtil.getSchemaJsonType(schema)))){
171+
continue;
172+
}
173+
168174
if (!state.isWalkEnabled()) {
169175
schemaErrors = schema.validate(node, rootNode, at);
170176
} else {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
114114
return unionTypeValidator.validate(node, rootNode, at);
115115
}
116116

117-
if (!equalsToSchemaType(node)) {
117+
//if (!equalsToSchemaType(node)) {
118+
if(!JsonNodeUtil.equalsToSchemaType(node,schemaType, parentSchema, super.config)){
118119
JsonType nodeType = TypeFactory.getValueNodeType(node, super.config);
119120
return Collections.singleton(buildValidationMessage(at, nodeType.toString(), schemaType.toString()));
120121
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
public class ValidatorState {
1919

20-
static final String VALIDATOR_STATE_KEY = "com.networknt.schema.ValidatorState";
20+
public static final String VALIDATOR_STATE_KEY = "com.networknt.schema.ValidatorState";
2121

2222
/**
2323
* Flag set when a node has matched Works in conjunction with the next flag:

src/main/java/com/networknt/schema/utils/JsonNodeUtil.java

Lines changed: 221 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22

33
import com.fasterxml.jackson.databind.JsonNode;
44
import com.fasterxml.jackson.databind.node.ArrayNode;
5-
import com.networknt.schema.CollectorContext;
6-
import com.networknt.schema.JsonType;
7-
import com.networknt.schema.SchemaValidatorsConfig;
8-
import com.networknt.schema.ValidatorState;
5+
import com.networknt.schema.*;
96

107
import java.util.Iterator;
118

129
public class JsonNodeUtil {
10+
private static final String TYPE = "type";
11+
private static final String ENUM = "enum";
12+
private static final String REF = "$ref";
13+
private static final String NULLABLE = "nullable";
1314

1415
public static boolean isNodeNullable(JsonNode schema){
15-
JsonNode nullable = schema.get("nullable");
16+
JsonNode nullable = schema.get(NULLABLE);
1617
if (nullable != null && nullable.asBoolean()) {
1718
return true;
1819
}
@@ -42,10 +43,224 @@ public static boolean matchOneOfTypeNode(JsonNode oneOfSchemaNode, JsonType node
4243
Iterator iterator = oneOfSchemaNode.elements();
4344
while (iterator.hasNext()){
4445
JsonNode oneOfTypeNode = (JsonNode) iterator.next();
45-
JsonNode typeTextNode = oneOfTypeNode.get("type");
46+
JsonNode typeTextNode = oneOfTypeNode.get(TYPE);
4647
if(typeTextNode != null && typeTextNode.asText().equals(nodeType.toString())) //If the nodeType is oneOf the type defined in the oneOf , return true
4748
return true;
4849
}
4950
return false;
5051
}
52+
53+
public static boolean equalsToSchemaType(JsonNode node, JsonSchema schema, SchemaValidatorsConfig config) {
54+
// in the case that node type is not the same as schema type, try to convert node to the
55+
// same type of schema. In REST API, query parameters, path parameters and headers are all
56+
// string type and we must convert, otherwise, all schema validations will fail.
57+
JsonType schemaType = getSchemaJsonType(schema);
58+
return equalsToSchemaType(node,schemaType,schema.getParentSchema(),config);
59+
60+
}
61+
62+
public static JsonType getSchemaJsonType(JsonSchema schema){
63+
JsonNode typeNode = schema.getSchemaNode().get(TYPE);
64+
if(typeNode!= null) return JsonType.valueOf(typeNode.asText().toUpperCase());
65+
return JsonType.UNKNOWN;
66+
}
67+
68+
69+
70+
public static boolean equalsToSchemaType(JsonNode node, JsonType schemaType, JsonSchema parentSchema, SchemaValidatorsConfig config) {
71+
// in the case that node type is not the same as schema type, try to convert node to the
72+
// same type of schema. In REST API, query parameters, path parameters and headers are all
73+
// string type and we must convert, otherwise, all schema validations will fail.
74+
75+
JsonType nodeType = TypeFactory.getValueNodeType(node, config);
76+
77+
if (nodeType != schemaType) {
78+
if (schemaType == JsonType.ANY) {
79+
return true;
80+
}
81+
82+
if (schemaType == JsonType.NUMBER && nodeType == JsonType.INTEGER) {
83+
return true;
84+
}
85+
86+
ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY);
87+
if(JsonType.NULL.equals(nodeType) ){
88+
if(state.isComplexValidator() && JsonNodeUtil.isNodeNullable(parentSchema.getParentSchema().getSchemaNode(), config) || JsonNodeUtil.isNodeNullable(parentSchema.getSchemaNode()) ){
89+
return true;
90+
}
91+
}
92+
93+
// Skip the type validation when the schema is an enum object schema. Since the current type
94+
// of node itself can be used for type validation.
95+
if (isEnumObjectSchema(parentSchema)) {
96+
return true;
97+
}
98+
if (config.isTypeLoose()) {
99+
// if typeLoose is true, everything can be a size 1 array
100+
if (schemaType == JsonType.ARRAY) {
101+
return true;
102+
}
103+
if (nodeType == JsonType.STRING) {
104+
if (schemaType == JsonType.INTEGER) {
105+
if (isInteger(node.textValue())) {
106+
return true;
107+
}
108+
} else if (schemaType == JsonType.BOOLEAN) {
109+
if (isBoolean(node.textValue())) {
110+
return true;
111+
}
112+
} else if (schemaType == JsonType.NUMBER) {
113+
if (isNumeric(node.textValue())) {
114+
return true;
115+
}
116+
}
117+
}
118+
}
119+
120+
return false;
121+
}
122+
return true;
123+
}
124+
public static boolean isInteger(String str) {
125+
if (str == null || str.equals("")) {
126+
return false;
127+
}
128+
129+
// all code below could be replaced with
130+
//return str.matrch("[-+]?(?:0|[1-9]\\d*)")
131+
int i = 0;
132+
if (str.charAt(0) == '-' || str.charAt(0) == '+') {
133+
if (str.length() == 1) {
134+
return false;
135+
}
136+
i = 1;
137+
}
138+
for (; i < str.length(); i++) {
139+
char c = str.charAt(i);
140+
if (c < '0' || c > '9') {
141+
return false;
142+
}
143+
}
144+
return true;
145+
}
146+
147+
public static boolean isBoolean(String s) {
148+
return "true".equals(s) || "false".equals(s);
149+
}
150+
151+
public static boolean isNumeric(String str) {
152+
if (str == null || str.equals("")) {
153+
return false;
154+
}
155+
156+
// all code below could be replaced with
157+
//return str.matrch("[-+]?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?")
158+
int i = 0;
159+
int len = str.length();
160+
161+
if (str.charAt(i) == MINUS || str.charAt(i) == PLUS) {
162+
if (str.length() == 1) {
163+
return false;
164+
}
165+
i = 1;
166+
}
167+
168+
char c = str.charAt(i++);
169+
170+
if (c == CHAR_0) {
171+
// TODO: if leading zeros are supported (counter to JSON spec) handle it here
172+
if (i < len) {
173+
c = str.charAt(i++);
174+
if (c != DOT && c != CHAR_E && c != CHAR_e) {
175+
return false;
176+
}
177+
}
178+
} else if (CHAR_1 <= c && c <= CHAR_9) {
179+
while (i < len && CHAR_0 <= c && c <= CHAR_9) {
180+
c = str.charAt(i++);
181+
}
182+
} else {
183+
return false;
184+
}
185+
186+
if (c == DOT) {
187+
if (i >= len) {
188+
return false;
189+
}
190+
c = str.charAt(i++);
191+
while (i < len && CHAR_0 <= c && c <= CHAR_9) {
192+
c = str.charAt(i++);
193+
}
194+
}
195+
196+
if (c == CHAR_E || c == CHAR_e) {
197+
if (i >= len) {
198+
return false;
199+
}
200+
c = str.charAt(i++);
201+
if (c == PLUS || c == MINUS) {
202+
if (i >= len) {
203+
return false;
204+
}
205+
c = str.charAt(i++);
206+
}
207+
while (i < len && CHAR_0 <= c && c <= CHAR_9) {
208+
c = str.charAt(i++);
209+
}
210+
}
211+
212+
return i >= len && (CHAR_0 <= c && c <= CHAR_9);
213+
}
214+
215+
private static final char CHAR_0 = '0';
216+
private static final char CHAR_1 = '1';
217+
private static final char CHAR_9 = '9';
218+
private static final char MINUS = '-';
219+
private static final char PLUS = '+';
220+
private static final char DOT = '.';
221+
private static final char CHAR_E = 'E';
222+
private static final char CHAR_e = 'e';
223+
224+
/**
225+
* Check if the type of the JsonNode's value is number based on the
226+
* status of typeLoose flag.
227+
*
228+
* @param node the JsonNode to check
229+
* @param config the SchemaValidatorsConfig to depend on
230+
* @return boolean to indicate if it is a number
231+
*/
232+
public static boolean isNumber(JsonNode node, SchemaValidatorsConfig config) {
233+
if (node.isNumber()) {
234+
return true;
235+
} else if (config.isTypeLoose()) {
236+
if (TypeFactory.getValueNodeType(node, config) == JsonType.STRING) {
237+
return isNumeric(node.textValue());
238+
}
239+
}
240+
return false;
241+
}
242+
243+
private static boolean isEnumObjectSchema(JsonSchema jsonSchema) {
244+
// There are three conditions for enum object schema
245+
// 1. The current schema contains key "type", and the value is object
246+
// 2. The current schema contains key "enum", and the value is an array
247+
// 3. The parent schema if refer from components, which means the corresponding enum object class would be generated
248+
JsonNode typeNode = null;
249+
JsonNode enumNode = null;
250+
JsonNode refNode = null;
251+
252+
if (jsonSchema != null) {
253+
if (jsonSchema.getSchemaNode() != null) {
254+
typeNode = jsonSchema.getSchemaNode().get(TYPE);
255+
enumNode = jsonSchema.getSchemaNode().get(ENUM);
256+
}
257+
if (jsonSchema.getParentSchema() != null && jsonSchema.getParentSchema().getSchemaNode() != null) {
258+
refNode = jsonSchema.getParentSchema().getSchemaNode().get(REF);
259+
}
260+
}
261+
if (typeNode != null && enumNode != null && refNode != null) {
262+
return TypeFactory.getSchemaNodeType(typeNode) == JsonType.OBJECT && enumNode.isArray();
263+
}
264+
return false;
265+
}
51266
}

0 commit comments

Comments
 (0)