Skip to content

Commit 96c3f83

Browse files
authored
Merge pull request #186 from networknt/issue185
Issue185
2 parents 2135906 + 8852fe5 commit 96c3f83

File tree

5 files changed

+103
-33
lines changed

5 files changed

+103
-33
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ public abstract class BaseJsonValidator implements JsonValidator {
3636
*/
3737
protected SchemaValidatorsConfig config;
3838

39+
/**
40+
* ThreadLocal to allow to pass state in recursive validator calls
41+
*/
42+
protected final static ThreadLocal<ValidatorState> validatorState = new ThreadLocal<ValidatorState>();
3943

4044
public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
4145
ValidatorTypeCode validatorType, ValidationContext validationContext) {

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

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -131,31 +131,46 @@ public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
131131
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
132132
debug(logger, node, rootNode, at);
133133

134-
// this validator considers a missing node as an error
135-
// set it here to true, however re-set it to its original value upon finishing the validation
136-
boolean missingNodeAsError = config.isMissingNodeAsError();
137-
config.setMissingNodeAsError(true);
134+
// this is a complex validator, we set the flag to true
135+
ValidatorState state = new ValidatorState();
136+
state.setComplexValidator(true);
137+
validatorState.set(state);
138138

139139
int numberOfValidSchema = 0;
140140
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
141141

142+
// validate that only a single element has been received in the oneOf node
143+
// validation should not continue, as it contradicts the oneOf requirement of only one
144+
if(node.isObject() && node.size()>1) {
145+
errors = Collections.singleton(buildValidationMessage(at, ""));
146+
return Collections.unmodifiableSet(errors);
147+
}
148+
142149
for (ShortcutValidator validator : schemas) {
143150
if (!validator.allConstantsMatch(node)) {
144151
// take a shortcut: if there is any constant that does not match,
145-
// we can bail out
152+
// we can bail out of the validation
146153
continue;
147154
}
155+
156+
// get the current validator
148157
JsonSchema schema = validator.schema;
149-
Set<ValidationMessage> schemaErrors = schema.validate(node, rootNode, at);
158+
Set<ValidationMessage> schemaErrors = schema.validate(node, rootNode, at);
159+
160+
// check if any validation errors have occurred
150161
if (schemaErrors.isEmpty()) {
162+
// check whether there are no errors HOWEVER we have validated the exact validator
163+
if(!state.hasMatchedNode())
164+
continue;
165+
151166
numberOfValidSchema++;
152167
errors = new LinkedHashSet<ValidationMessage>();
153-
}
154-
if(numberOfValidSchema == 0){
155-
errors.addAll(schemaErrors);
156-
}
168+
} else {
169+
errors.addAll(schemaErrors);
170+
}
157171
}
158172

173+
// no valid schema has been found after validating all schema validators
159174
if (numberOfValidSchema == 0) {
160175
for (Iterator<ValidationMessage> it = errors.iterator(); it.hasNext();) {
161176
ValidationMessage msg = it.next();
@@ -168,14 +183,18 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
168183
// ensure there is always an error reported if number of valid schemas is 0
169184
errors.add(buildValidationMessage(at, ""));
170185
}
186+
} else {
187+
errors.clear();
171188
}
189+
190+
// validated upfront
172191
if (numberOfValidSchema > 1) {
173192
errors = Collections.singleton(buildValidationMessage(at, ""));
174193
}
175194

176-
// reset the flag for error handling
177-
config.setMissingNodeAsError(missingNodeAsError);
178-
195+
// reset the ValidatorState object in the ThreadLocal
196+
validatorState.remove();
197+
179198
return Collections.unmodifiableSet(errors);
180199
}
181200

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

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,48 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
4141

4242
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
4343

44+
// get the Validator state object storing validation data
45+
ValidatorState state = validatorState.get();
46+
if(state == null) {
47+
// if one has not been created, instantiate one
48+
state = new ValidatorState();
49+
validatorState.set(state);
50+
}
51+
4452
for (Map.Entry<String, JsonSchema> entry : schemas.entrySet()) {
4553
JsonSchema propertySchema = entry.getValue();
4654
JsonNode propertyNode = node.get(entry.getKey());
4755

4856
if (propertyNode != null) {
57+
// check whether this is a complex validator. save the state
58+
boolean isComplex = state.isComplexValidator();
59+
// if this is a complex validator, the node has matched, and all it's child elements, if available, are to be validated
60+
if(state.isComplexValidator()) {
61+
state.setMatchedNode(true);
62+
}
63+
// reset the complex validator for child element validation, and reset it after the return from the recursive call
64+
state.setComplexValidator(false);
65+
66+
//validate the child element(s)
4967
errors.addAll(propertySchema.validate(propertyNode, rootNode, at + "." + entry.getKey()));
68+
69+
// reset the complex flag to the original value before the recursive call
70+
state.setComplexValidator(isComplex);
71+
// if this was a complex validator, the node has matched and has been validated
72+
if(state.isComplexValidator()) {
73+
state.setMatchedNode(true);
74+
}
5075
} else {
51-
// if a node could not be found, treat is as error/continue, depending on the SchemaValidatorsConfig
52-
if(config.isMissingNodeAsError()) {
53-
if(getParentSchema().hasRequiredValidator())
54-
errors.addAll(getParentSchema().getRequiredValidator().validate(node, rootNode, at));
55-
else
56-
errors.add(buildValidationMessage(at, node.toString()));
76+
// decide which behavior to eomploy when validator has not matched
77+
if(state.isComplexValidator()) {
78+
// this was a complex validator (ex oneOf) and the node has not been matched
79+
state.setMatchedNode(false);
80+
return Collections.unmodifiableSet(new LinkedHashSet<ValidationMessage>());
5781
}
82+
83+
// check whether the node which has not matched was mandatory or not
84+
if(getParentSchema().hasRequiredValidator())
85+
errors.addAll(getParentSchema().getRequiredValidator().validate(node, rootNode, at));
5886
}
5987
}
6088

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ public class SchemaValidatorsConfig {
2525
*/
2626
private boolean typeLoose;
2727

28-
/**
29-
* if IS_MISSING_NODE_AS_ERROR = true, the validator will ignore the missing node.
30-
* if set to false, then the validator will report an error
31-
*/
32-
private boolean missingNodeAsError = false;
33-
3428
/**
3529
* Map of public, normally internet accessible schema URLs to alternate locations; this allows for offline
3630
* validation of schemas that refer to public URLs. This is merged with any mappings the {@link JsonSchemaFactory}
@@ -65,14 +59,6 @@ public void setUriMappings(Map<String, String> uriMappings) {
6559
this.uriMappings = uriMappings;
6660
}
6761

68-
public boolean isMissingNodeAsError() {
69-
return missingNodeAsError;
70-
}
71-
72-
public void setMissingNodeAsError(boolean missingNodeAsError) {
73-
this.missingNodeAsError = missingNodeAsError;
74-
}
75-
7662
public boolean isHandleNullableField() {
7763
return handleNullableField;
7864
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.networknt.schema;
2+
3+
public class ValidatorState {
4+
/**
5+
* Flag set when a node has matched Works in conjunction with the next flag:
6+
* isComplexValidator, to be used for complex validators such as oneOf, for ex
7+
*/
8+
private boolean matchedNode = true;
9+
10+
/**
11+
* Flag set if complex validators such as oneOf, for ex, neeed to have their
12+
* properties validated. The PropertiesValidator is not aware generally of a
13+
* complex validator is being validated or a simple poperty tree
14+
*/
15+
private boolean isComplexValidator = false;
16+
17+
public void setMatchedNode(boolean matchedNode) {
18+
this.matchedNode = matchedNode;
19+
}
20+
21+
public boolean hasMatchedNode() {
22+
return matchedNode;
23+
}
24+
25+
public boolean isComplexValidator() {
26+
return isComplexValidator;
27+
}
28+
29+
public void setComplexValidator(boolean isComplexValidator) {
30+
this.isComplexValidator = isComplexValidator;
31+
}
32+
33+
}

0 commit comments

Comments
 (0)