Skip to content

Commit d61d006

Browse files
Properly support propertyNames
- 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
1 parent dfd63aa commit d61d006

File tree

8 files changed

+171
-43
lines changed

8 files changed

+171
-43
lines changed

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

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,60 +15,40 @@
1515
*/
1616
package com.networknt.schema;
1717

18-
import com.fasterxml.jackson.databind.JsonNode;
18+
import java.util.Collections;
19+
import java.util.Iterator;
20+
import java.util.LinkedHashSet;
21+
import java.util.Set;
22+
1923
import org.slf4j.Logger;
2024
import org.slf4j.LoggerFactory;
2125

22-
import java.util.*;
23-
import java.util.regex.Pattern;
26+
import com.fasterxml.jackson.databind.JsonNode;
27+
import com.fasterxml.jackson.databind.node.TextNode;
2428

2529
public class PropertyNamesValidator extends BaseJsonValidator implements JsonValidator {
2630
private static final Logger logger = LoggerFactory.getLogger(PropertyNamesValidator.class);
27-
private Map<String, JsonSchema> schemas;
28-
private boolean schemaValue = false;
31+
private final JsonSchema innerSchema;
2932
public PropertyNamesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
3033
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PROPERTYNAMES, validationContext);
31-
if(schemaNode.isBoolean()) {
32-
schemaValue = schemaNode.booleanValue();
33-
} else {
34-
schemas = new HashMap<String, JsonSchema>();
35-
for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
36-
String pname = it.next();
37-
schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, parentSchema.getCurrentUri(), schemaNode.get(pname), parentSchema));
38-
}
39-
}
34+
innerSchema = new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), schemaNode, parentSchema);
4035
}
4136

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

4540
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
46-
if(schemas != null) {
47-
for (Map.Entry<String, JsonSchema> entry : schemas.entrySet()) {
48-
JsonNode propertyNode = node.get(entry.getKey());
49-
// check propertyNames
50-
if (!node.isObject()) {
51-
continue;
52-
}
53-
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
54-
String pname = it.next();
55-
int maxLength = entry.getValue().getSchemaNode().intValue();
56-
if("maxLength".equals(entry.getKey()) && pname.length() > maxLength) {
57-
errors.add(buildValidationMessage(at + "." + pname, "maxLength " + maxLength));
58-
}
59-
int minLength = entry.getValue().getSchemaNode().intValue();
60-
if("minLength".equals(entry.getKey()) && pname.length() < minLength) {
61-
errors.add(buildValidationMessage(at + "." + pname, "minLength " + minLength));
62-
}
63-
String pattern = entry.getValue().getSchemaNode().textValue();
64-
if("pattern".equals(entry.getKey()) && !Pattern.matches(pattern,pname)) {
65-
errors.add(buildValidationMessage(at + "." + pname, "pattern " + pattern));
66-
}
67-
}
68-
}
69-
} else {
70-
if(!schemaValue && node.isObject() && node.size() != 0) {
71-
errors.add(buildValidationMessage(at + "." + node, "false"));
41+
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
42+
final String pname = it.next();
43+
final TextNode pnameText = TextNode.valueOf(pname);
44+
final Set<ValidationMessage> schemaErrors = innerSchema.validate(pnameText, node, at + "." + pname);
45+
for (final ValidationMessage schemaError : schemaErrors) {
46+
final String path = schemaError.getPath();
47+
String msg = schemaError.getMessage();
48+
if (msg.startsWith(path))
49+
msg = msg.substring(path.length()).replaceFirst("^:\\s*", "");
50+
51+
errors.add(buildValidationMessage(schemaError.getPath(), msg));
7252
}
7353
}
7454
return Collections.unmodifiableSet(errors);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.networknt.schema;
2+
3+
import java.io.InputStream;
4+
import java.util.Set;
5+
6+
import org.junit.Assert;
7+
import org.junit.Test;
8+
9+
import com.fasterxml.jackson.databind.JsonNode;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
12+
public class Issue342Test {
13+
protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) {
14+
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
15+
return factory.getSchema(schemaContent);
16+
}
17+
18+
protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
19+
ObjectMapper mapper = new ObjectMapper();
20+
JsonNode node = mapper.readTree(content);
21+
return node;
22+
}
23+
24+
@Test
25+
public void propertyNameEnumShouldFailV7() throws Exception {
26+
String schemaPath = "/schema/issue342-v7.json";
27+
String dataPath = "/data/issue342.json";
28+
InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
29+
JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
30+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
31+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
32+
Set<ValidationMessage> errors = schema.validate(node);
33+
Assert.assertEquals(1, errors.size());
34+
final ValidationMessage error = errors.iterator().next();
35+
Assert.assertEquals("$.z", error.getPath());
36+
Assert.assertEquals("Property name $.z is not valid for validation: does not have a value in the enumeration [a, b, c]", error.getMessage());
37+
}
38+
}

src/test/java/com/networknt/schema/Issue375Test.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ public void shouldFailAndShowValidationValuesWithError() throws Exception {
5353
}
5454

5555
List<String> expectedMessages = Arrays.asList(
56-
"Property name $.fields.longName123 is not valid for validation: maxLength 5",
57-
"Property name $.fields.longName123 is not valid for validation: pattern ^[a-zA-Z]+$",
58-
"Property name $.fields.a is not valid for validation: minLength 3");
56+
"Property name $.fields.longName123 is not valid for validation: may only be 5 characters long",
57+
"Property name $.fields.longName123 is not valid for validation: does not match the regex pattern ^[a-zA-Z]+$",
58+
"Property name $.fields.a is not valid for validation: must be at least 3 characters long");
5959
MatcherAssert.assertThat(errorMessages, Matchers.containsInAnyOrder(expectedMessages.toArray()));
6060
}
6161
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.networknt.schema;
2+
3+
import java.io.InputStream;
4+
import java.util.HashSet;
5+
import java.util.Set;
6+
import java.util.stream.Collectors;
7+
8+
import org.junit.Assert;
9+
import org.junit.Test;
10+
11+
import com.fasterxml.jackson.databind.JsonNode;
12+
import com.fasterxml.jackson.databind.ObjectMapper;
13+
14+
public class Issue396Test {
15+
protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) {
16+
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
17+
return factory.getSchema(schemaContent);
18+
}
19+
20+
protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
21+
ObjectMapper mapper = new ObjectMapper();
22+
JsonNode node = mapper.readTree(content);
23+
return node;
24+
}
25+
26+
@Test
27+
public void testComplexPropertyNamesV7() throws Exception {
28+
String schemaPath = "/schema/issue396-v7.json";
29+
String dataPath = "/data/issue396.json";
30+
InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
31+
JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream);
32+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
33+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
34+
35+
final Set<String> invalidPaths = new HashSet<>();
36+
node.fields().forEachRemaining(entry -> {
37+
if (!entry.getValue().asBoolean())
38+
invalidPaths.add("$." + entry.getKey());
39+
});
40+
41+
Set<ValidationMessage> errors = schema.validate(node);
42+
final Set<String> failedPaths = errors.stream().map(ValidationMessage::getPath).collect(Collectors.toSet());
43+
Assert.assertEquals(failedPaths, invalidPaths);
44+
}
45+
}

src/test/resources/data/issue342.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"a": "ok",
3+
"z": "nope"
4+
}

src/test/resources/data/issue396.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"veryveryverylongallowedname": true,
3+
"a": true,
4+
"x": true,
5+
"z": false,
6+
"abc": false,
7+
"w": false,
8+
"ww": false,
9+
"WW": true
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"propertyNames": {
5+
"enum": [
6+
"a",
7+
"b",
8+
"c"
9+
]
10+
}
11+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"type": "object",
3+
"propertyNames": {
4+
"anyOf": [
5+
{
6+
"minLength": 10
7+
},
8+
{
9+
"$ref": "#/definitions/props"
10+
},
11+
{
12+
"oneOf": [
13+
{
14+
"enum": [
15+
"z",
16+
"a",
17+
"b"
18+
]
19+
},
20+
{
21+
"$ref": "#/definitions/xyz"
22+
}
23+
]
24+
}
25+
]
26+
},
27+
"definitions": {
28+
"props": {
29+
"minLength": 2,
30+
"pattern": "[A-Z]"
31+
},
32+
"xyz": {
33+
"enum": [
34+
"x",
35+
"y",
36+
"z"
37+
]
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)