Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enables unit-tests for the unevaluatedItems keyword. #749

Merged
merged 2 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions src/main/java/com/networknt/schema/AllOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,40 +26,44 @@
public class AllOfValidator extends BaseJsonValidator {
private static final Logger logger = LoggerFactory.getLogger(AllOfValidator.class);

private final List<JsonSchema> schemas = new ArrayList<JsonSchema>();
private final List<JsonSchema> schemas = new ArrayList<>();

public AllOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ALL_OF, validationContext);
this.validationContext = validationContext;
int size = schemaNode.size();
for (int i = 0; i < size; i++) {
schemas.add(new JsonSchema(validationContext,
this.schemas.add(new JsonSchema(validationContext,
schemaPath + "/" + i,
parentSchema.getCurrentUri(),
schemaNode.get(i),
parentSchema));
}
}

@Override
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
debug(logger, node, rootNode, at);
CollectorContext collectorContext = CollectorContext.getInstance();

// get the Validator state object storing validation data
ValidatorState state = (ValidatorState) collectorContext.get(ValidatorState.VALIDATOR_STATE_KEY);

Set<ValidationMessage> childSchemaErrors = new LinkedHashSet<ValidationMessage>();
Set<ValidationMessage> childSchemaErrors = new LinkedHashSet<>();

Collection<String> newEvaluatedItems = Collections.emptyList();
Collection<String> newEvaluatedProperties = Collections.emptyList();

for (JsonSchema schema : schemas) {
// As AllOf might contain multiple schemas take a backup of evaluatedProperties.
for (JsonSchema schema : this.schemas) {
// As AllOf might contain multiple schemas take a backup of evaluated stuff.
Collection<String> backupEvaluatedItems = collectorContext.getEvaluatedItems();
Collection<String> backupEvaluatedProperties = collectorContext.getEvaluatedProperties();

Set<ValidationMessage> localErrors = new HashSet<>();

try {
// Make the evaluatedProperties list empty.
// Make the evaluated lists empty.
collectorContext.resetEvaluatedItems();
collectorContext.resetEvaluatedProperties();

if (!state.isWalkEnabled()) {
Expand All @@ -72,30 +76,31 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String

// Keep Collecting total evaluated properties.
if (localErrors.isEmpty()) {
newEvaluatedItems = collectorContext.getEvaluatedItems();
newEvaluatedProperties = collectorContext.getEvaluatedProperties();
}

if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
final Iterator<JsonNode> arrayElements = schemaNode.elements();
final Iterator<JsonNode> arrayElements = this.schemaNode.elements();
while (arrayElements.hasNext()) {
final ObjectNode allOfEntry = (ObjectNode) arrayElements.next();
final JsonNode $ref = allOfEntry.get("$ref");
if (null != $ref) {
final ValidationContext.DiscriminatorContext currentDiscriminatorContext = validationContext
final ValidationContext.DiscriminatorContext currentDiscriminatorContext = this.validationContext
.getCurrentDiscriminatorContext();
if (null != currentDiscriminatorContext) {
final ObjectNode discriminator = currentDiscriminatorContext
.getDiscriminatorForPath(allOfEntry.get("$ref").asText());
if (null != discriminator) {
registerAndMergeDiscriminator(currentDiscriminatorContext, discriminator, parentSchema, at);
registerAndMergeDiscriminator(currentDiscriminatorContext, discriminator, this.parentSchema, at);
// now we have to check whether we have hit the right target
final String discriminatorPropertyName = discriminator.get("propertyName").asText();
final JsonNode discriminatorNode = node.get(discriminatorPropertyName);
final String discriminatorPropertyValue = discriminatorNode == null
? null
: discriminatorNode.textValue();

final JsonSchema jsonSchema = parentSchema;
final JsonSchema jsonSchema = this.parentSchema;
checkDiscriminatorMatch(
currentDiscriminatorContext,
discriminator,
Expand All @@ -107,10 +112,13 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
}
}
} finally {
collectorContext.setEvaluatedItems(backupEvaluatedItems);
collectorContext.setEvaluatedProperties(backupEvaluatedProperties);
if (localErrors.isEmpty()) {
collectorContext.getEvaluatedItems().addAll(newEvaluatedItems);
collectorContext.getEvaluatedProperties().addAll(newEvaluatedProperties);
}
newEvaluatedItems = Collections.emptyList();
newEvaluatedProperties = Collections.emptyList();
}
}
Expand All @@ -123,7 +131,7 @@ public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at,
if (shouldValidateSchema) {
return validate(node, rootNode, at);
}
for (JsonSchema schema : schemas) {
for (JsonSchema schema : this.schemas) {
// Walk through the schema
schema.walk(node, rootNode, at, false);
}
Expand All @@ -132,6 +140,6 @@ public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at,

@Override
public void preloadJsonSchema() {
preloadJsonSchemas(schemas);
preloadJsonSchemas(this.schemas);
}
}
35 changes: 20 additions & 15 deletions src/main/java/com/networknt/schema/AnyOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
this.validationContext = validationContext;
int size = schemaNode.size();
for (int i = 0; i < size; i++) {
schemas.add(new JsonSchema(validationContext,
this.schemas.add(new JsonSchema(validationContext,
schemaPath + "/" + i,
parentSchema.getCurrentUri(),
schemaNode.get(i),
Expand All @@ -49,6 +49,7 @@ public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
}
}

@Override
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
debug(logger, node, rootNode, at);
CollectorContext collectorContext = CollectorContext.getInstance();
Expand All @@ -57,28 +58,30 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
ValidatorState state = (ValidatorState) collectorContext.get(ValidatorState.VALIDATOR_STATE_KEY);

if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
validationContext.enterDiscriminatorContext(this.discriminatorContext, at);
this.validationContext.enterDiscriminatorContext(this.discriminatorContext, at);
}

boolean initialHasMatchedNode = state.hasMatchedNode();

Set<ValidationMessage> allErrors = new LinkedHashSet<>();

// As anyOf might contain multiple schemas take a backup of evaluatedProperties.
// As anyOf might contain multiple schemas take a backup of evaluated stuff.
Collection<String> backupEvaluatedItems = collectorContext.getEvaluatedItems();
Collection<String> backupEvaluatedProperties = collectorContext.getEvaluatedProperties();

// Make the evaluatedProperties list empty.
// Make the evaluated lists empty.
collectorContext.resetEvaluatedItems();
collectorContext.resetEvaluatedProperties();

try {
int numberOfValidSubSchemas = 0;
for (int i = 0; i < schemas.size(); ++i) {
JsonSchema schema = schemas.get(i);
for (int i = 0; i < this.schemas.size(); ++i) {
JsonSchema schema = this.schemas.get(i);
state.setMatchedNode(initialHasMatchedNode);
Set<ValidationMessage> errors;
String typeValidatorName = schemas.get(i).getSchemaPath() + "/type";
if (schema.getValidators().containsKey(typeValidatorName)) {
TypeValidator typeValidator = ((TypeValidator) schema.getValidators().get(typeValidatorName));

if (schema.hasTypeValidator()) {
TypeValidator typeValidator = schema.getTypeValidator();
//If schema has type validator and node type doesn't match with schemaType then ignore it
//For union type, it is a must to call TypeValidator
if (typeValidator.getSchemaType() != JsonType.UNION && !typeValidator.equalsToSchemaType(node)) {
Expand Down Expand Up @@ -108,7 +111,7 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
// return empty errors.
return errors;
} else if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
if (discriminatorContext.isDiscriminatorMatchFound()) {
if (this.discriminatorContext.isDiscriminatorMatchFound()) {
if (!errors.isEmpty()) {
errors.add(buildValidationMessage(at, DISCRIMINATOR_REMARK));
allErrors.addAll(errors);
Expand All @@ -130,20 +133,22 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
allErrors = childNotRequiredErrors;
}

if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators() && discriminatorContext.isActive()) {
final Set<ValidationMessage> errors = new HashSet<ValidationMessage>();
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators() && this.discriminatorContext.isActive()) {
final Set<ValidationMessage> errors = new HashSet<>();
errors.add(buildValidationMessage(at, "based on the provided discriminator. No alternative could be chosen based on the discriminator property"));
return Collections.unmodifiableSet(errors);
}
} finally {
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
validationContext.leaveDiscriminatorContextImmediately(at);
this.validationContext.leaveDiscriminatorContextImmediately(at);
}
if (allErrors.isEmpty()) {
state.setMatchedNode(true);
} else {
collectorContext.getEvaluatedItems().clear();
collectorContext.getEvaluatedProperties().clear();
}
collectorContext.getEvaluatedItems().addAll(backupEvaluatedItems);
collectorContext.getEvaluatedProperties().addAll(backupEvaluatedProperties);
}
return Collections.unmodifiableSet(allErrors);
Expand All @@ -154,14 +159,14 @@ public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at,
if (shouldValidateSchema) {
return validate(node, rootNode, at);
}
for (JsonSchema schema : schemas) {
for (JsonSchema schema : this.schemas) {
schema.walk(node, rootNode, at, false);
}
return new LinkedHashSet<>();
}

@Override
public void preloadJsonSchema() {
preloadJsonSchemas(schemas);
preloadJsonSchemas(this.schemas);
}
}
64 changes: 47 additions & 17 deletions src/main/java/com/networknt/schema/CollectorContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,54 @@ public static CollectorContext getInstance() {
/**
* Map for holding the name and {@link Collector} or a simple Object.
*/
private Map<String, Object> collectorMap = new HashMap<String, Object>();
private Map<String, Object> collectorMap = new HashMap<>();

/**
* Map for holding the name and {@link Collector} class collect method output.
*/
private Map<String, Object> collectorLoadMap = new HashMap<String, Object>();
private Map<String, Object> collectorLoadMap = new HashMap<>();

/**
* Used to track which array items have been evaluated.
*/
private Collection<String> evaluatedItems = new ArrayList<>();

/**
* Used to track which properties have been evaluated.
*/
private Collection<String> evaluatedProperties = new ArrayList<>();

/**
* Identifies which array items have been evaluated.
*
* @return the set of evaluated items (never null)
*/
public Collection<String> getEvaluatedItems() {
return this.evaluatedItems;
}

/**
* Set the array items that have been evaluated.
* @param paths the set of evaluated array items (may be null)
*/
public void setEvaluatedItems(Collection<String> paths) {
this.evaluatedItems = null != paths ? paths : new ArrayList<>();
}

/**
* Replaces the array items that have been evaluated with an empty collection.
*/
public void resetEvaluatedItems() {
this.evaluatedItems = new ArrayList<>();
}

/**
* Identifies which properties have been evaluated.
*
* @return the set of evaluated properties (never null)
*/
public Collection<String> getEvaluatedProperties() {
return evaluatedProperties;
return this.evaluatedProperties;
}

/**
Expand All @@ -85,7 +114,7 @@ public void resetEvaluatedProperties() {
* @param collector Collector
*/
public <E> void add(String name, Collector<E> collector) {
collectorMap.put(name, collector);
this.collectorMap.put(name, collector);
}

/**
Expand All @@ -96,7 +125,7 @@ public <E> void add(String name, Collector<E> collector) {
* @param name String
*/
public <E> void add(String name, Object object) {
collectorMap.put(name, object);
this.collectorMap.put(name, object);
}

/**
Expand All @@ -114,21 +143,21 @@ public <E> void add(String name, Object object) {
* @return Object
*/
public Object get(String name) {
Object object = collectorMap.get(name);
if (object instanceof Collector<?> && (collectorLoadMap.get(name) != null)) {
return collectorLoadMap.get(name);
Object object = this.collectorMap.get(name);
if (object instanceof Collector<?> && (this.collectorLoadMap.get(name) != null)) {
return this.collectorLoadMap.get(name);
}
return collectorMap.get(name);
return this.collectorMap.get(name);
}

/**
* Returns all the collected data. Please look into {@link #get(String)} method for more details.
* @return Map
*/
public Map<String, Object> getAll() {
Map<String, Object> mergedMap = new HashMap<String, Object>();
mergedMap.putAll(collectorMap);
mergedMap.putAll(collectorLoadMap);
Map<String, Object> mergedMap = new HashMap<>();
mergedMap.putAll(this.collectorMap);
mergedMap.putAll(this.collectorLoadMap);
return mergedMap;
}

Expand All @@ -139,7 +168,7 @@ public Map<String, Object> getAll() {
* @param data Object
*/
public void combineWithCollector(String name, Object data) {
Object object = collectorMap.get(name);
Object object = this.collectorMap.get(name);
if (object instanceof Collector<?>) {
Collector<?> collector = (Collector<?>) object;
collector.combine(data);
Expand All @@ -150,20 +179,21 @@ public void combineWithCollector(String name, Object data) {
* Reset the context
*/
public void reset() {
this.collectorMap = new HashMap<String, Object>();
this.collectorLoadMap = new HashMap<String, Object>();
this.collectorMap = new HashMap<>();
this.collectorLoadMap = new HashMap<>();
this.evaluatedItems.clear();
this.evaluatedProperties.clear();
}

/**
* Loads data from all collectors.
*/
void loadCollectors() {
Set<Entry<String, Object>> entrySet = collectorMap.entrySet();
Set<Entry<String, Object>> entrySet = this.collectorMap.entrySet();
for (Entry<String, Object> entry : entrySet) {
if (entry.getValue() instanceof Collector<?>) {
Collector<?> collector = (Collector<?>) entry.getValue();
collectorLoadMap.put(entry.getKey(), collector.collect());
this.collectorLoadMap.put(entry.getKey(), collector.collect());
}
}

Expand Down
Loading