Skip to content

Commit

Permalink
Add dependentRequired and dependentSchemas validators (#479)
Browse files Browse the repository at this point in the history
  • Loading branch information
kmalski committed Nov 9, 2021
1 parent 1f7d062 commit 2dee3ae
Show file tree
Hide file tree
Showing 8 changed files with 420 additions and 1 deletion.
68 changes: 68 additions & 0 deletions src/main/java/com/networknt/schema/DependentRequired.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2016 Network New Technologies Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class DependentRequired extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(DependentRequired.class);
private final Map<String, List<String>> propertyDependencies = new HashMap<String, List<String>>();

public DependentRequired(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {

super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENT_REQUIRED, validationContext);

for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
String pname = it.next();
JsonNode pvalue = schemaNode.get(pname);
if (pvalue.isArray()) {
List<String> dependencies = propertyDependencies.computeIfAbsent(pname, k -> new ArrayList<>());

for (int i = 0; i < pvalue.size(); i++) {
dependencies.add(pvalue.get(i).asText());
}
}
}

parseErrorCode(getValidatorType().getErrorCodeKey());
}

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

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

for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
String pname = it.next();
List<String> dependencies = propertyDependencies.get(pname);
if (dependencies != null && !dependencies.isEmpty()) {
for (String field : dependencies) {
if (node.get(field) == null) {
errors.add(buildValidationMessage(at, propertyDependencies.toString()));
}
}
}
}

return Collections.unmodifiableSet(errors);
}

}
64 changes: 64 additions & 0 deletions src/main/java/com/networknt/schema/DependentSchemas.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2016 Network New Technologies Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class DependentSchemas extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(DependentSchemas.class);
private final Map<String, JsonSchema> schemaDependencies = new HashMap<String, JsonSchema>();

public DependentSchemas(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {

super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENT_SCHEMAS, validationContext);

for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
String pname = it.next();
JsonNode pvalue = schemaNode.get(pname);
if (pvalue.isObject() || pvalue.isBoolean()) {
schemaDependencies.put(pname, new JsonSchema(validationContext, pname, parentSchema.getCurrentUri(), pvalue, parentSchema));
}
}

parseErrorCode(getValidatorType().getErrorCodeKey());
}

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

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

for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
String pname = it.next();
JsonSchema schema = schemaDependencies.get(pname);
if (schema != null) {
errors.addAll(schema.validate(node, rootNode, at));
}
}

return Collections.unmodifiableSet(errors);
}

@Override
public void preloadJsonSchema() {
preloadJsonSchemas(schemaDependencies.values());
}
}
4 changes: 3 additions & 1 deletion src/main/java/com/networknt/schema/ValidatorTypeCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSc
FALSE("false", "1041", new MessageFormat(I18nSupport.getString("false")), FalseValidator.class, 14),
CONST("const", "1042", new MessageFormat(I18nSupport.getString("const")), ConstValidator.class, 14),
CONTAINS("contains", "1043", new MessageFormat(I18nSupport.getString("contains")), ContainsValidator.class, 14),
PROPERTYNAMES("propertyNames", "1044", new MessageFormat(I18nSupport.getString("propertyNames")), PropertyNamesValidator.class, 14);
PROPERTYNAMES("propertyNames", "1044", new MessageFormat(I18nSupport.getString("propertyNames")), PropertyNamesValidator.class, 14),
DEPENDENT_REQUIRED("dependentRequired", "1045", new MessageFormat(I18nSupport.getString("dependentRequired")), DependentRequired.class, 8), // V201909
DEPENDENT_SCHEMAS("dependentSchemas", "1046", new MessageFormat(I18nSupport.getString("dependentSchemas")), DependentSchemas.class, 8); // V201909

private static Map<String, ValidatorTypeCode> constants = new HashMap<String, ValidatorTypeCode>();
private static SpecVersion specVersion = new SpecVersion();
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/jsv-messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ allOf = {0}: should be valid to all the schemas {1}
anyOf = {0}: should be valid to any of the schemas {1}
crossEdits = {0}: has an error with 'cross edits'
dependencies = {0}: has an error with dependencies {1}
dependentRequired = {0}: has a missing property which is dependent required {1}
dependentSchemas = {0}: has an error with dependentSchemas {1}
edits = {0}: has an error with 'edits'
enum = {0}: does not have a value in the enumeration {1}
format = {0}: does not match the {1} pattern {2}
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/jsv-messages_zh_CN.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ allOf = {0}\uFF1A\u5E94\u8BE5\u5BF9\u6240\u6709\u6A21\u5F0F {1} \u90FD\u6709\u65
anyOf = {0}\uFF1A\u5E94\u8BE5\u5BF9\u4EFB\u4F55\u67B6\u6784 {1} \u90FD\u6709\u6548
crossEdits = {0}\uFF1A\u201C\u4EA4\u53C9\u7F16\u8F91\u201D\u6709\u9519\u8BEF
dependencies = {0}\uFF1A\u4F9D\u8D56\u9879 {1} \u6709\u9519\u8BEF
dependentRequired = {0}\u7f3a\u5c11\u4f9d\u8d56\u9879\u6240\u9700\u7684\u5c5e\u6027 {1}
dependentSchemas = {0}\u4f9d\u8d56\u6a21\u5f0f {1} \u6709\u9519\u8BEF
edits = {0}\uFF1A\u201C\u7F16\u8F91\u201D\u6709\u9519\u8BEF
enum = {0}\uFF1A\u679A\u4E3E {1} \u4E2D\u6CA1\u6709\u503C
format = {0}\uFF1A\u4E0E {1} \u6A21\u5F0F {2} \u4E0D\u5339\u914D
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/com/networknt/schema/V201909JsonSchemaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -336,5 +336,15 @@ public void testUniqueItemsValidator() throws Exception {
runTestFile("draft2019-09/uniqueItems.json");
}

@Test
public void testDependentRequiredValidator() throws Exception {
runTestFile("draft2019-09/dependentRequired.json");
}

@Test
public void testDependentSchemasValidator() throws Exception {
runTestFile("draft2019-09/dependentSchemas.json");
}

}

142 changes: 142 additions & 0 deletions src/test/resources/draft2019-09/dependentRequired.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
[
{
"description": "single dependency",
"schema": {"dependentRequired": {"bar": ["foo"]}},
"tests": [
{
"description": "neither",
"data": {},
"valid": true
},
{
"description": "nondependant",
"data": {"foo": 1},
"valid": true
},
{
"description": "with dependency",
"data": {"foo": 1, "bar": 2},
"valid": true
},
{
"description": "missing dependency",
"data": {"bar": 2},
"valid": false
},
{
"description": "ignores arrays",
"data": ["bar"],
"valid": true
},
{
"description": "ignores strings",
"data": "foobar",
"valid": true
},
{
"description": "ignores other non-objects",
"data": 12,
"valid": true
}
]
},
{
"description": "empty dependents",
"schema": {"dependentRequired": {"bar": []}},
"tests": [
{
"description": "empty object",
"data": {},
"valid": true
},
{
"description": "object with one property",
"data": {"bar": 2},
"valid": true
},
{
"description": "non-object is valid",
"data": 1,
"valid": true
}
]
},
{
"description": "multiple dependents required",
"schema": {"dependentRequired": {"quux": ["foo", "bar"]}},
"tests": [
{
"description": "neither",
"data": {},
"valid": true
},
{
"description": "nondependants",
"data": {"foo": 1, "bar": 2},
"valid": true
},
{
"description": "with dependencies",
"data": {"foo": 1, "bar": 2, "quux": 3},
"valid": true
},
{
"description": "missing dependency",
"data": {"foo": 1, "quux": 2},
"valid": false
},
{
"description": "missing other dependency",
"data": {"bar": 1, "quux": 2},
"valid": false
},
{
"description": "missing both dependencies",
"data": {"quux": 1},
"valid": false
}
]
},
{
"description": "dependencies with escaped characters",
"schema": {
"dependentRequired": {
"foo\nbar": ["foo\rbar"],
"foo\"bar": ["foo'bar"]
}
},
"tests": [
{
"description": "CRLF",
"data": {
"foo\nbar": 1,
"foo\rbar": 2
},
"valid": true
},
{
"description": "quoted quotes",
"data": {
"foo'bar": 1,
"foo\"bar": 2
},
"valid": true
},
{
"description": "CRLF missing dependent",
"data": {
"foo\nbar": 1,
"foo": 2
},
"valid": false
},
{
"description": "quoted quotes missing dependent",
"data": {
"foo\"bar": 2
},
"valid": false
}
]
}
]
Loading

0 comments on commit 2dee3ae

Please sign in to comment.