Skip to content

Commit

Permalink
Properly support propertyNames
Browse files Browse the repository at this point in the history
- implement property names in terms of full schema validation
- add test case for complex property name
- added test case for networknt#342
- fix networknt#375 tests to expect standard messages
- closes networknt#396
- closes networknt#342
  • Loading branch information
JonasProgrammer committed Apr 11, 2021
1 parent dfd63aa commit d61d006
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 43 deletions.
60 changes: 20 additions & 40 deletions src/main/java/com/networknt/schema/PropertyNamesValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,60 +15,40 @@
*/
package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.regex.Pattern;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.TextNode;

public class PropertyNamesValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(PropertyNamesValidator.class);
private Map<String, JsonSchema> schemas;
private boolean schemaValue = false;
private final JsonSchema innerSchema;
public PropertyNamesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PROPERTYNAMES, validationContext);
if(schemaNode.isBoolean()) {
schemaValue = schemaNode.booleanValue();
} else {
schemas = new HashMap<String, JsonSchema>();
for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
String pname = it.next();
schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, parentSchema.getCurrentUri(), schemaNode.get(pname), parentSchema));
}
}
innerSchema = new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), schemaNode, parentSchema);
}

public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
debug(logger, node, rootNode, at);

Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
if(schemas != null) {
for (Map.Entry<String, JsonSchema> entry : schemas.entrySet()) {
JsonNode propertyNode = node.get(entry.getKey());
// check propertyNames
if (!node.isObject()) {
continue;
}
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
String pname = it.next();
int maxLength = entry.getValue().getSchemaNode().intValue();
if("maxLength".equals(entry.getKey()) && pname.length() > maxLength) {
errors.add(buildValidationMessage(at + "." + pname, "maxLength " + maxLength));
}
int minLength = entry.getValue().getSchemaNode().intValue();
if("minLength".equals(entry.getKey()) && pname.length() < minLength) {
errors.add(buildValidationMessage(at + "." + pname, "minLength " + minLength));
}
String pattern = entry.getValue().getSchemaNode().textValue();
if("pattern".equals(entry.getKey()) && !Pattern.matches(pattern,pname)) {
errors.add(buildValidationMessage(at + "." + pname, "pattern " + pattern));
}
}
}
} else {
if(!schemaValue && node.isObject() && node.size() != 0) {
errors.add(buildValidationMessage(at + "." + node, "false"));
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
final String pname = it.next();
final TextNode pnameText = TextNode.valueOf(pname);
final Set<ValidationMessage> schemaErrors = innerSchema.validate(pnameText, node, at + "." + pname);
for (final ValidationMessage schemaError : schemaErrors) {
final String path = schemaError.getPath();
String msg = schemaError.getMessage();
if (msg.startsWith(path))
msg = msg.substring(path.length()).replaceFirst("^:\\s*", "");

errors.add(buildValidationMessage(schemaError.getPath(), msg));
}
}
return Collections.unmodifiableSet(errors);
Expand Down
38 changes: 38 additions & 0 deletions src/test/java/com/networknt/schema/Issue342Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.networknt.schema;

import java.io.InputStream;
import java.util.Set;

import org.junit.Assert;
import org.junit.Test;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Issue342Test {
protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) {
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
return factory.getSchema(schemaContent);
}

protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(content);
return node;
}

@Test
public void propertyNameEnumShouldFailV7() throws Exception {
String schemaPath = "/schema/issue342-v7.json";
String dataPath = "/data/issue342.json";
InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
Set<ValidationMessage> errors = schema.validate(node);
Assert.assertEquals(1, errors.size());
final ValidationMessage error = errors.iterator().next();
Assert.assertEquals("$.z", error.getPath());
Assert.assertEquals("Property name $.z is not valid for validation: does not have a value in the enumeration [a, b, c]", error.getMessage());
}
}
6 changes: 3 additions & 3 deletions src/test/java/com/networknt/schema/Issue375Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ public void shouldFailAndShowValidationValuesWithError() throws Exception {
}

List<String> expectedMessages = Arrays.asList(
"Property name $.fields.longName123 is not valid for validation: maxLength 5",
"Property name $.fields.longName123 is not valid for validation: pattern ^[a-zA-Z]+$",
"Property name $.fields.a is not valid for validation: minLength 3");
"Property name $.fields.longName123 is not valid for validation: may only be 5 characters long",
"Property name $.fields.longName123 is not valid for validation: does not match the regex pattern ^[a-zA-Z]+$",
"Property name $.fields.a is not valid for validation: must be at least 3 characters long");
MatcherAssert.assertThat(errorMessages, Matchers.containsInAnyOrder(expectedMessages.toArray()));
}
}
45 changes: 45 additions & 0 deletions src/test/java/com/networknt/schema/Issue396Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.networknt.schema;

import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import org.junit.Assert;
import org.junit.Test;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Issue396Test {
protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) {
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
return factory.getSchema(schemaContent);
}

protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(content);
return node;
}

@Test
public void testComplexPropertyNamesV7() throws Exception {
String schemaPath = "/schema/issue396-v7.json";
String dataPath = "/data/issue396.json";
InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);

final Set<String> invalidPaths = new HashSet<>();
node.fields().forEachRemaining(entry -> {
if (!entry.getValue().asBoolean())
invalidPaths.add("$." + entry.getKey());
});

Set<ValidationMessage> errors = schema.validate(node);
final Set<String> failedPaths = errors.stream().map(ValidationMessage::getPath).collect(Collectors.toSet());
Assert.assertEquals(failedPaths, invalidPaths);
}
}
4 changes: 4 additions & 0 deletions src/test/resources/data/issue342.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"a": "ok",
"z": "nope"
}
10 changes: 10 additions & 0 deletions src/test/resources/data/issue396.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"veryveryverylongallowedname": true,
"a": true,
"x": true,
"z": false,
"abc": false,
"w": false,
"ww": false,
"WW": true
}
11 changes: 11 additions & 0 deletions src/test/resources/schema/issue342-v7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"propertyNames": {
"enum": [
"a",
"b",
"c"
]
}
}
40 changes: 40 additions & 0 deletions src/test/resources/schema/issue396-v7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"type": "object",
"propertyNames": {
"anyOf": [
{
"minLength": 10
},
{
"$ref": "#/definitions/props"
},
{
"oneOf": [
{
"enum": [
"z",
"a",
"b"
]
},
{
"$ref": "#/definitions/xyz"
}
]
}
]
},
"definitions": {
"props": {
"minLength": 2,
"pattern": "[A-Z]"
},
"xyz": {
"enum": [
"x",
"y",
"z"
]
}
}
}

0 comments on commit d61d006

Please sign in to comment.