Skip to content

Commit 2dee3ae

Browse files
authored
Add dependentRequired and dependentSchemas validators (#479)
1 parent 1f7d062 commit 2dee3ae

File tree

8 files changed

+420
-1
lines changed

8 files changed

+420
-1
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (c) 2016 Network New Technologies Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.networknt.schema;
18+
19+
import com.fasterxml.jackson.databind.JsonNode;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
import java.util.*;
24+
25+
public class DependentRequired extends BaseJsonValidator implements JsonValidator {
26+
private static final Logger logger = LoggerFactory.getLogger(DependentRequired.class);
27+
private final Map<String, List<String>> propertyDependencies = new HashMap<String, List<String>>();
28+
29+
public DependentRequired(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
30+
31+
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENT_REQUIRED, validationContext);
32+
33+
for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
34+
String pname = it.next();
35+
JsonNode pvalue = schemaNode.get(pname);
36+
if (pvalue.isArray()) {
37+
List<String> dependencies = propertyDependencies.computeIfAbsent(pname, k -> new ArrayList<>());
38+
39+
for (int i = 0; i < pvalue.size(); i++) {
40+
dependencies.add(pvalue.get(i).asText());
41+
}
42+
}
43+
}
44+
45+
parseErrorCode(getValidatorType().getErrorCodeKey());
46+
}
47+
48+
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
49+
debug(logger, node, rootNode, at);
50+
51+
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
52+
53+
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
54+
String pname = it.next();
55+
List<String> dependencies = propertyDependencies.get(pname);
56+
if (dependencies != null && !dependencies.isEmpty()) {
57+
for (String field : dependencies) {
58+
if (node.get(field) == null) {
59+
errors.add(buildValidationMessage(at, propertyDependencies.toString()));
60+
}
61+
}
62+
}
63+
}
64+
65+
return Collections.unmodifiableSet(errors);
66+
}
67+
68+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2016 Network New Technologies Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.networknt.schema;
18+
19+
import com.fasterxml.jackson.databind.JsonNode;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
import java.util.*;
24+
25+
public class DependentSchemas extends BaseJsonValidator implements JsonValidator {
26+
private static final Logger logger = LoggerFactory.getLogger(DependentSchemas.class);
27+
private final Map<String, JsonSchema> schemaDependencies = new HashMap<String, JsonSchema>();
28+
29+
public DependentSchemas(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
30+
31+
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENT_SCHEMAS, validationContext);
32+
33+
for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
34+
String pname = it.next();
35+
JsonNode pvalue = schemaNode.get(pname);
36+
if (pvalue.isObject() || pvalue.isBoolean()) {
37+
schemaDependencies.put(pname, new JsonSchema(validationContext, pname, parentSchema.getCurrentUri(), pvalue, parentSchema));
38+
}
39+
}
40+
41+
parseErrorCode(getValidatorType().getErrorCodeKey());
42+
}
43+
44+
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
45+
debug(logger, node, rootNode, at);
46+
47+
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
48+
49+
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
50+
String pname = it.next();
51+
JsonSchema schema = schemaDependencies.get(pname);
52+
if (schema != null) {
53+
errors.addAll(schema.validate(node, rootNode, at));
54+
}
55+
}
56+
57+
return Collections.unmodifiableSet(errors);
58+
}
59+
60+
@Override
61+
public void preloadJsonSchema() {
62+
preloadJsonSchemas(schemaDependencies.values());
63+
}
64+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSc
7272
FALSE("false", "1041", new MessageFormat(I18nSupport.getString("false")), FalseValidator.class, 14),
7373
CONST("const", "1042", new MessageFormat(I18nSupport.getString("const")), ConstValidator.class, 14),
7474
CONTAINS("contains", "1043", new MessageFormat(I18nSupport.getString("contains")), ContainsValidator.class, 14),
75-
PROPERTYNAMES("propertyNames", "1044", new MessageFormat(I18nSupport.getString("propertyNames")), PropertyNamesValidator.class, 14);
75+
PROPERTYNAMES("propertyNames", "1044", new MessageFormat(I18nSupport.getString("propertyNames")), PropertyNamesValidator.class, 14),
76+
DEPENDENT_REQUIRED("dependentRequired", "1045", new MessageFormat(I18nSupport.getString("dependentRequired")), DependentRequired.class, 8), // V201909
77+
DEPENDENT_SCHEMAS("dependentSchemas", "1046", new MessageFormat(I18nSupport.getString("dependentSchemas")), DependentSchemas.class, 8); // V201909
7678

7779
private static Map<String, ValidatorTypeCode> constants = new HashMap<String, ValidatorTypeCode>();
7880
private static SpecVersion specVersion = new SpecVersion();

src/main/resources/jsv-messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ allOf = {0}: should be valid to all the schemas {1}
33
anyOf = {0}: should be valid to any of the schemas {1}
44
crossEdits = {0}: has an error with 'cross edits'
55
dependencies = {0}: has an error with dependencies {1}
6+
dependentRequired = {0}: has a missing property which is dependent required {1}
7+
dependentSchemas = {0}: has an error with dependentSchemas {1}
68
edits = {0}: has an error with 'edits'
79
enum = {0}: does not have a value in the enumeration {1}
810
format = {0}: does not match the {1} pattern {2}

src/main/resources/jsv-messages_zh_CN.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ allOf = {0}\uFF1A\u5E94\u8BE5\u5BF9\u6240\u6709\u6A21\u5F0F {1} \u90FD\u6709\u65
33
anyOf = {0}\uFF1A\u5E94\u8BE5\u5BF9\u4EFB\u4F55\u67B6\u6784 {1} \u90FD\u6709\u6548
44
crossEdits = {0}\uFF1A\u201C\u4EA4\u53C9\u7F16\u8F91\u201D\u6709\u9519\u8BEF
55
dependencies = {0}\uFF1A\u4F9D\u8D56\u9879 {1} \u6709\u9519\u8BEF
6+
dependentRequired = {0}\u7f3a\u5c11\u4f9d\u8d56\u9879\u6240\u9700\u7684\u5c5e\u6027 {1}
7+
dependentSchemas = {0}\u4f9d\u8d56\u6a21\u5f0f {1} \u6709\u9519\u8BEF
68
edits = {0}\uFF1A\u201C\u7F16\u8F91\u201D\u6709\u9519\u8BEF
79
enum = {0}\uFF1A\u679A\u4E3E {1} \u4E2D\u6CA1\u6709\u503C
810
format = {0}\uFF1A\u4E0E {1} \u6A21\u5F0F {2} \u4E0D\u5339\u914D

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,5 +336,15 @@ public void testUniqueItemsValidator() throws Exception {
336336
runTestFile("draft2019-09/uniqueItems.json");
337337
}
338338

339+
@Test
340+
public void testDependentRequiredValidator() throws Exception {
341+
runTestFile("draft2019-09/dependentRequired.json");
342+
}
343+
344+
@Test
345+
public void testDependentSchemasValidator() throws Exception {
346+
runTestFile("draft2019-09/dependentSchemas.json");
347+
}
348+
339349
}
340350

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
[
2+
{
3+
"description": "single dependency",
4+
"schema": {"dependentRequired": {"bar": ["foo"]}},
5+
"tests": [
6+
{
7+
"description": "neither",
8+
"data": {},
9+
"valid": true
10+
},
11+
{
12+
"description": "nondependant",
13+
"data": {"foo": 1},
14+
"valid": true
15+
},
16+
{
17+
"description": "with dependency",
18+
"data": {"foo": 1, "bar": 2},
19+
"valid": true
20+
},
21+
{
22+
"description": "missing dependency",
23+
"data": {"bar": 2},
24+
"valid": false
25+
},
26+
{
27+
"description": "ignores arrays",
28+
"data": ["bar"],
29+
"valid": true
30+
},
31+
{
32+
"description": "ignores strings",
33+
"data": "foobar",
34+
"valid": true
35+
},
36+
{
37+
"description": "ignores other non-objects",
38+
"data": 12,
39+
"valid": true
40+
}
41+
]
42+
},
43+
{
44+
"description": "empty dependents",
45+
"schema": {"dependentRequired": {"bar": []}},
46+
"tests": [
47+
{
48+
"description": "empty object",
49+
"data": {},
50+
"valid": true
51+
},
52+
{
53+
"description": "object with one property",
54+
"data": {"bar": 2},
55+
"valid": true
56+
},
57+
{
58+
"description": "non-object is valid",
59+
"data": 1,
60+
"valid": true
61+
}
62+
]
63+
},
64+
{
65+
"description": "multiple dependents required",
66+
"schema": {"dependentRequired": {"quux": ["foo", "bar"]}},
67+
"tests": [
68+
{
69+
"description": "neither",
70+
"data": {},
71+
"valid": true
72+
},
73+
{
74+
"description": "nondependants",
75+
"data": {"foo": 1, "bar": 2},
76+
"valid": true
77+
},
78+
{
79+
"description": "with dependencies",
80+
"data": {"foo": 1, "bar": 2, "quux": 3},
81+
"valid": true
82+
},
83+
{
84+
"description": "missing dependency",
85+
"data": {"foo": 1, "quux": 2},
86+
"valid": false
87+
},
88+
{
89+
"description": "missing other dependency",
90+
"data": {"bar": 1, "quux": 2},
91+
"valid": false
92+
},
93+
{
94+
"description": "missing both dependencies",
95+
"data": {"quux": 1},
96+
"valid": false
97+
}
98+
]
99+
},
100+
{
101+
"description": "dependencies with escaped characters",
102+
"schema": {
103+
"dependentRequired": {
104+
"foo\nbar": ["foo\rbar"],
105+
"foo\"bar": ["foo'bar"]
106+
}
107+
},
108+
"tests": [
109+
{
110+
"description": "CRLF",
111+
"data": {
112+
"foo\nbar": 1,
113+
"foo\rbar": 2
114+
},
115+
"valid": true
116+
},
117+
{
118+
"description": "quoted quotes",
119+
"data": {
120+
"foo'bar": 1,
121+
"foo\"bar": 2
122+
},
123+
"valid": true
124+
},
125+
{
126+
"description": "CRLF missing dependent",
127+
"data": {
128+
"foo\nbar": 1,
129+
"foo": 2
130+
},
131+
"valid": false
132+
},
133+
{
134+
"description": "quoted quotes missing dependent",
135+
"data": {
136+
"foo\"bar": 2
137+
},
138+
"valid": false
139+
}
140+
]
141+
}
142+
]

0 commit comments

Comments
 (0)