Skip to content

Commit 0f983b0

Browse files
authored
Refactor walk (#986)
* Allow apply defaults to resolve ref and refactor walk listeners * Refactor * Refactor * Refactor * Refactor * Refactor * Refactor * Refactor * Update docs * Refactor * Refactor * Refactor * Add test
1 parent eea61d6 commit 0f983b0

28 files changed

+1250
-298
lines changed

doc/upgrading.md

+24-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,30 @@ This contains information on the notable or breaking changes in each version.
66

77
### 1.4.0
88

9-
This contains breaking changes in how custom meta-schemas are created.
9+
This contains breaking changes
10+
- to those using the walk functionality
11+
- in how custom meta-schemas are created
12+
13+
When using the walker with defaults the `default` across a `$ref` are properly resolved and used.
14+
15+
The behavior for the property listener is now more consistent whether or not validation is enabled. Previously if validation is enabled but the property is `null` the property listener is not called while if validation is not enabled it will be called. Now the property listener will be called in both scenarios.
16+
17+
The following are the breaking changes to those using the walk functionality.
18+
19+
`WalkEvent`
20+
| Field | Change | Notes
21+
|--------------------------|--------------|----------
22+
| `schemaLocation` | Removed | For keywords: `getValidator().getSchemaLocation()`. For items and properties: `getSchema().getSchemaLocation()`
23+
| `evaluationPath` | Removed | For keywords: `getValidator().getEvaluationPath()`. For items and properties: `getSchema().getEvaluationPath()`
24+
| `schemaNode` | Removed | `getSchema().getSchemaNode()`
25+
| `parentSchema` | Removed | `getSchema().getParentSchema()`
26+
| `schema` | New | For keywords this is the parent schema of the validator. For items and properties this is the item or property schema being evaluated.
27+
| `node` | Renamed | `instanceNode`
28+
| `currentJsonSchemaFactory`| Removed | `getSchema().getValidationContext().getJsonSchemaFactory()`
29+
| `validator` | New | The validator indicated by the keyword.
30+
31+
32+
The following are the breaking changes in how custom meta-schemas are created.
1033

1134
`JsonSchemaFactory`
1235
* The following were renamed on `JsonSchemaFactory` builder

doc/walkers.md

+40-45
Original file line numberDiff line numberDiff line change
@@ -38,65 +38,61 @@ public interface JsonSchemaWalker {
3838
The JSONValidator interface extends this new interface thus allowing all the validator's defined in library to implement this new interface. BaseJsonValidator class provides a default implementation of the walk method. In this case the walk method does nothing but validating based on shouldValidateSchema parameter.
3939

4040
```java
41-
/**
41+
/**
4242
* This is default implementation of walk method. Its job is to call the
4343
* validate method if shouldValidateSchema is enabled.
4444
*/
4545
@Override
46-
public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
47-
JsonNodePath instanceLocation, boolean shouldValidateSchema);
48-
Set<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();
49-
if (shouldValidateSchema) {
50-
validationMessages = validate(executionContext, node, rootNode, instanceLocation);
51-
}
52-
return validationMessages;
46+
default Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
47+
JsonNodePath instanceLocation, boolean shouldValidateSchema) {
48+
return shouldValidateSchema ? validate(executionContext, node, rootNode, instanceLocation)
49+
: Collections.emptySet();
5350
}
54-
5551
```
5652

5753
A new walk method added to the JSONSchema class allows us to walk through the JSONSchema.
5854

5955
```java
60-
public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) {
61-
// Create the collector context object.
62-
CollectorContext collectorContext = new CollectorContext();
63-
// Set the collector context in thread info, this is unique for every thread.
64-
ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext);
65-
Set<ValidationMessage> errors = walk(node, node, AT_ROOT, shouldValidateSchema);
66-
// Load all the data from collectors into the context.
67-
collectorContext.loadCollectors();
68-
// Collect errors and collector context into validation result.
69-
ValidationResult validationResult = new ValidationResult(errors, collectorContext);
70-
return validationResult;
56+
public ValidationResult walk(JsonNode node, boolean validate) {
57+
return walk(createExecutionContext(), node, validate);
7158
}
7259

7360
@Override
74-
public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
75-
Set<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();
61+
public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
62+
JsonNodePath instanceLocation, boolean shouldValidateSchema) {
63+
Set<ValidationMessage> errors = new LinkedHashSet<>();
7664
// Walk through all the JSONWalker's.
77-
for (Entry<String, JsonValidator> entry : validators.entrySet()) {
78-
JsonWalker jsonWalker = entry.getValue();
79-
String schemaPathWithKeyword = entry.getKey();
65+
for (JsonValidator validator : getValidators()) {
66+
JsonNodePath evaluationPathWithKeyword = validator.getEvaluationPath();
8067
try {
81-
// Call all the pre-walk listeners. If all the pre-walk listeners return true
82-
// then continue to walk method.
83-
if (keywordWalkListenerRunner.runPreWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath,
84-
schemaNode, parentSchema)) {
85-
validationMessages.addAll(jsonWalker.walk(node, rootNode, at, shouldValidateSchema));
68+
// Call all the pre-walk listeners. If at least one of the pre walk listeners
69+
// returns SKIP, then skip the walk.
70+
if (this.validationContext.getConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext,
71+
evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation,
72+
this, validator)) {
73+
Set<ValidationMessage> results = null;
74+
try {
75+
results = validator.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
76+
} finally {
77+
if (results != null && !results.isEmpty()) {
78+
errors.addAll(results);
79+
}
80+
}
8681
}
8782
} finally {
8883
// Call all the post-walk listeners.
89-
keywordWalkListenerRunner.runPostWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath,
90-
schemaNode, parentSchema, validationMessages);
84+
this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext,
85+
evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation,
86+
this, validator, errors);
9187
}
9288
}
93-
return validationMessages;
89+
return errors;
9490
}
9591
```
9692
Following code snippet shows how to call the walk method on a JsonSchema instance.
9793

98-
```
99-
ValidationResult result = jsonSchema.walk(data,false);
94+
```java
95+
ValidationResult result = jsonSchema.walk(data, false);
10096

10197
```
10298

@@ -122,7 +118,7 @@ private static class PropertiesKeywordListener implements JsonSchemaWalkListener
122118

123119
@Override
124120
public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) {
125-
JsonNode schemaNode = keywordWalkEvent.getSchemaNode();
121+
JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode();
126122
if (schemaNode.get("title").textValue().equals("Property3")) {
127123
return WalkFlow.SKIP;
128124
}
@@ -146,7 +142,7 @@ SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
146142
schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(),
147143
new PropertiesKeywordListener());
148144
final JsonSchemaFactory schemaFactory = JsonSchemaFactory
149-
.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema)
145+
.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema)
150146
.build();
151147
this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig);
152148

@@ -160,7 +156,7 @@ Both property walk listeners and keyword walk listener can be modeled by using t
160156
SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
161157
schemaValidatorsConfig.addPropertyWalkListener(new ExamplePropertyWalkListener());
162158
final JsonSchemaFactory schemaFactory = JsonSchemaFactory
163-
.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema)
159+
.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema)
164160
.build();
165161
this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig);
166162

@@ -176,16 +172,15 @@ Following snippet shows the details captured by WalkEvent instance.
176172

177173
```java
178174
public class WalkEvent {
179-
180175
private ExecutionContext executionContext;
181-
private SchemaLocation schemaLocation;
182-
private JsonNodePath evaluationPath;
183-
private JsonNode schemaNode;
184-
private JsonSchema parentSchema;
176+
private JsonSchema schema;
185177
private String keyword;
186-
private JsonNode node;
187178
private JsonNode rootNode;
179+
private JsonNode instanceNode;
188180
private JsonNodePath instanceLocation;
181+
private JsonValidator validator;
182+
...
183+
}
189184
```
190185

191186
### Sample Flow
@@ -285,7 +280,7 @@ But if we apply defaults while walking, then required validation passes, and the
285280
JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
286281
SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
287282
schemaValidatorsConfig.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true));
288-
JsonSchema jsonSchema = schemaFactory.getSchema(getClass().getClassLoader().getResourceAsStream("schema.json"), schemaValidatorsConfig);
283+
JsonSchema jsonSchema = schemaFactory.getSchema(SchemaLocation.of("classpath:schema.json"), schemaValidatorsConfig);
289284

290285
JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data.json"));
291286
ValidationResult result = jsonSchema.walk(inputNode, true);

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@
2727
*/
2828
public class ConstValidator extends BaseJsonValidator implements JsonValidator {
2929
private static final Logger logger = LoggerFactory.getLogger(ConstValidator.class);
30-
JsonNode schemaNode;
3130

32-
public ConstValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
31+
public ConstValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
32+
JsonSchema parentSchema, ValidationContext validationContext) {
3333
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.CONST, validationContext);
34-
this.schemaNode = schemaNode;
3534
}
3635

3736
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
public class DynamicRefValidator extends BaseJsonValidator {
2929
private static final Logger logger = LoggerFactory.getLogger(DynamicRefValidator.class);
3030

31-
protected JsonSchemaRef schema;
31+
protected final JsonSchemaRef schema;
3232

3333
public DynamicRefValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
3434
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.DYNAMIC_REF, validationContext);

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

+20-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.fasterxml.jackson.databind.JsonNode;
2020
import com.fasterxml.jackson.databind.node.ArrayNode;
2121
import com.networknt.schema.annotation.JsonNodeAnnotation;
22+
import com.networknt.schema.utils.JsonSchemaRefs;
2223
import com.networknt.schema.utils.SetView;
2324

2425
import org.slf4j.Logger;
@@ -35,7 +36,7 @@ public class ItemsValidator extends BaseJsonValidator {
3536

3637
private final JsonSchema schema;
3738
private final List<JsonSchema> tupleSchema;
38-
private Boolean additionalItems;
39+
private final Boolean additionalItems;
3940
private final JsonSchema additionalSchema;
4041

4142
private Boolean hasUnevaluatedItemsValidator = null;
@@ -47,6 +48,8 @@ public class ItemsValidator extends BaseJsonValidator {
4748
public ItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
4849
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext);
4950

51+
Boolean additionalItems = null;
52+
5053
this.tupleSchema = new ArrayList<>();
5154
JsonSchema foundSchema = null;
5255
JsonSchema foundAdditionalSchema = null;
@@ -66,14 +69,15 @@ public ItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath
6669
if (addItemNode != null) {
6770
additionalItemsSchemaNode = addItemNode;
6871
if (addItemNode.isBoolean()) {
69-
this.additionalItems = addItemNode.asBoolean();
72+
additionalItems = addItemNode.asBoolean();
7073
} else if (addItemNode.isObject()) {
7174
foundAdditionalSchema = validationContext.newSchema(
7275
parentSchema.schemaLocation.append(PROPERTY_ADDITIONAL_ITEMS),
7376
parentSchema.evaluationPath.append(PROPERTY_ADDITIONAL_ITEMS), addItemNode, parentSchema);
7477
}
7578
}
7679
}
80+
this.additionalItems = additionalItems;
7781
this.schema = foundSchema;
7882
this.additionalSchema = foundAdditionalSchema;
7983
this.additionalItemsEvaluationPath = parentSchema.evaluationPath.append(PROPERTY_ADDITIONAL_ITEMS);
@@ -205,7 +209,7 @@ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode n
205209
JsonNode defaultNode = null;
206210
if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()
207211
&& this.schema != null) {
208-
defaultNode = this.schema.getSchemaNode().get("default");
212+
defaultNode = getDefaultNode(this.schema);
209213
}
210214
int i = 0;
211215
for (JsonNode n : arrayNode) {
@@ -222,6 +226,17 @@ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode n
222226
return validationMessages;
223227
}
224228

229+
private static JsonNode getDefaultNode(JsonSchema schema) {
230+
JsonNode result = schema.getSchemaNode().get("default");
231+
if (result == null) {
232+
JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
233+
if (schemaRef != null) {
234+
result = getDefaultNode(schemaRef.getSchema());
235+
}
236+
}
237+
return result;
238+
}
239+
225240
private void doWalk(ExecutionContext executionContext, HashSet<ValidationMessage> validationMessages, int i, JsonNode node,
226241
JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
227242
if (this.schema != null) {
@@ -247,14 +262,12 @@ private void doWalk(ExecutionContext executionContext, HashSet<ValidationMessage
247262
private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode,
248263
JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages) {
249264
boolean executeWalk = this.validationContext.getConfig().getItemWalkListenerRunner().runPreWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(),
250-
node, rootNode, instanceLocation, walkSchema.getEvaluationPath(), walkSchema.getSchemaLocation(),
251-
walkSchema.getSchemaNode(), walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory());
265+
node, rootNode, instanceLocation, walkSchema, this);
252266
if (executeWalk) {
253267
validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema));
254268
}
255269
this.validationContext.getConfig().getItemWalkListenerRunner().runPostWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(), node, rootNode,
256-
instanceLocation, this.evaluationPath, walkSchema.getSchemaLocation(),
257-
walkSchema.getSchemaNode(), walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory(), validationMessages);
270+
instanceLocation, walkSchema, this, validationMessages);
258271

259272
}
260273

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

+16-10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.fasterxml.jackson.databind.JsonNode;
2020
import com.fasterxml.jackson.databind.node.ArrayNode;
2121
import com.networknt.schema.annotation.JsonNodeAnnotation;
22+
import com.networknt.schema.utils.JsonSchemaRefs;
2223
import com.networknt.schema.utils.SetView;
2324

2425
import org.slf4j.Logger;
@@ -119,7 +120,7 @@ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode n
119120
JsonNode defaultNode = null;
120121
if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()
121122
&& this.schema != null) {
122-
defaultNode = this.schema.getSchemaNode().get("default");
123+
defaultNode = getDefaultNode(this.schema);
123124
}
124125
for (int i = this.prefixCount; i < node.size(); ++i) {
125126
JsonNode n = node.get(i);
@@ -139,6 +140,17 @@ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode n
139140
return validationMessages;
140141
}
141142

143+
private static JsonNode getDefaultNode(JsonSchema schema) {
144+
JsonNode result = schema.getSchemaNode().get("default");
145+
if (result == null) {
146+
JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
147+
if (schemaRef != null) {
148+
result = getDefaultNode(schemaRef.getSchema());
149+
}
150+
}
151+
return result;
152+
}
153+
142154
private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode,
143155
JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages) {
144156
//@formatter:off
@@ -148,10 +160,7 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema
148160
node,
149161
rootNode,
150162
instanceLocation,
151-
walkSchema.getEvaluationPath(),
152-
walkSchema.getSchemaLocation(),
153-
walkSchema.getSchemaNode(),
154-
walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory()
163+
walkSchema, this
155164
);
156165
if (executeWalk) {
157166
validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema));
@@ -162,11 +171,8 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema
162171
node,
163172
rootNode,
164173
instanceLocation,
165-
this.evaluationPath,
166-
walkSchema.getSchemaLocation(),
167-
walkSchema.getSchemaNode(),
168-
walkSchema.getParentSchema(),
169-
this.validationContext, this.validationContext.getJsonSchemaFactory(), validationMessages
174+
walkSchema,
175+
this, validationMessages
170176
);
171177
//@formatter:on
172178
}

0 commit comments

Comments
 (0)