From 01dfc0be6ec4868fffb7e515bc075753345efb59 Mon Sep 17 00:00:00 2001 From: Anderson Ferreira Date: Wed, 6 Nov 2019 10:56:39 -0300 Subject: [PATCH 1/2] Implementing IF-THEN-ELSE Conditional (Draft 7) --- src/main/java/com/networknt/schema/JsonSchema.java | 6 +++--- src/main/java/com/networknt/schema/ValidatorTypeCode.java | 3 ++- src/test/java/com/networknt/schema/JsonSchemaTest.java | 7 +++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 569a10804..8a59beea7 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -145,13 +145,13 @@ private Map read(JsonNode schemaNode) { Iterator pnames = schemaNode.fieldNames(); while (pnames.hasNext()) { String pname = pnames.next(); - JsonNode n = schemaNode.get(pname); + JsonNode nodeToUse = pname.equals("if") ? schemaNode : schemaNode.get(pname); - JsonValidator validator = validationContext.newValidator(getSchemaPath(), pname, n, this); + JsonValidator validator = validationContext.newValidator(getSchemaPath(), pname, nodeToUse, this); if (validator != null) { validators.put(getSchemaPath() + "/" + pname, validator); - if(pname.equals("required")) + if (pname.equals("required")) requiredValidator = validator; } diff --git a/src/main/java/com/networknt/schema/ValidatorTypeCode.java b/src/main/java/com/networknt/schema/ValidatorTypeCode.java index b82a91e5a..31b8251ab 100644 --- a/src/main/java/com/networknt/schema/ValidatorTypeCode.java +++ b/src/main/java/com/networknt/schema/ValidatorTypeCode.java @@ -65,7 +65,8 @@ public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSc UNIQUE_ITEMS("uniqueItems", "1031", new MessageFormat("{0}: the items in the array must be unique"), UniqueItemsValidator.class), DATETIME("date-time", "1034", new MessageFormat("{0}: {1} is an invalid {2}"), null), UUID("uuid", "1035", new MessageFormat("{0}: {1} is an invalid {2}"), null), - ID("id", "1036", new MessageFormat("{0}: {1} is an invalid segment for URI {2}"), null); + ID("id", "1036", new MessageFormat("{0}: {1} is an invalid segment for URI {2}"), null), + IF_THEN_ELSE("if", "1037", null, IfValidator.class); private static Map constants = new HashMap(); diff --git a/src/test/java/com/networknt/schema/JsonSchemaTest.java b/src/test/java/com/networknt/schema/JsonSchemaTest.java index cf17f5229..887092a9c 100644 --- a/src/test/java/com/networknt/schema/JsonSchemaTest.java +++ b/src/test/java/com/networknt/schema/JsonSchemaTest.java @@ -320,13 +320,16 @@ public void testSchemaFromClasspath() throws Exception { runTestFile("tests/classpath/schema.json"); } - - @Test public void testUUIDValidator() throws Exception { runTestFile("tests/uuid.json"); } + @Test + public void testIfValidator() throws Exception { + runTestFile("tests/if.json"); + } + /** * Although, the data file has three errors, but only on is reported */ From 2f37b9e91ad6f8f57995954a7b2a5f850302c16a Mon Sep 17 00:00:00 2001 From: Anderson Ferreira Date: Wed, 6 Nov 2019 10:57:30 -0300 Subject: [PATCH 2/2] Adding Unit Tests for IfValidator --- .../com/networknt/schema/IfValidator.java | 64 ++++++ src/test/resources/tests/if.json | 217 ++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 src/main/java/com/networknt/schema/IfValidator.java create mode 100644 src/test/resources/tests/if.json diff --git a/src/main/java/com/networknt/schema/IfValidator.java b/src/main/java/com/networknt/schema/IfValidator.java new file mode 100644 index 000000000..f0c66132a --- /dev/null +++ b/src/main/java/com/networknt/schema/IfValidator.java @@ -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 IfValidator extends BaseJsonValidator implements JsonValidator { + private static final Logger logger = LoggerFactory.getLogger(IfValidator.class); + + private static final ArrayList KEYWORDS = new ArrayList(Arrays.asList("if", "then", "else")); + + private JsonSchema ifSchema; + private JsonSchema thenSchema; + private JsonSchema elseSchema; + + public IfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { + super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.IF_THEN_ELSE, validationContext); + + for (final String keyword : KEYWORDS) { + final JsonNode node = schemaNode.get(keyword); + if (keyword.equals("if")) { + ifSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema); + } else if (keyword.equals("then") && node != null) { + thenSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema); + } else if (keyword.equals("else") && node != null) { + elseSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema); + } + } + } + + public Set validate(JsonNode node, JsonNode rootNode, String at) { + debug(logger, node, rootNode, at); + + Set errors = new LinkedHashSet(); + + Set ifErrors = ifSchema.validate(node, rootNode, at); + if (ifErrors.isEmpty() && thenSchema != null) { + errors.addAll(thenSchema.validate(node, rootNode, at)); + } else if (thenSchema != null && elseSchema != null) { + errors.addAll(elseSchema.validate(node, rootNode, at)); + } + + return Collections.unmodifiableSet(errors); + } + +} diff --git a/src/test/resources/tests/if.json b/src/test/resources/tests/if.json new file mode 100644 index 000000000..f82ba4c41 --- /dev/null +++ b/src/test/resources/tests/if.json @@ -0,0 +1,217 @@ +[ + { + "description": "Simple if (incomplete structure, without THEN and ELSE, no validation is performed)", + "schema": { + "then": { + "properties": { + "foo": { "maximum": 3.0 } + } + }, + "else": { + "properties": { + "foo": { "maximum": 3.0 } + } + } + }, + "tests": [ + { + "description": "if valid", + "data": { "foo": 2.9 }, + "valid": true + }, + { + "description": "if invalid", + "data": { "foo": 3.1 }, + "valid": true + } + ] + }, + { + "description": "Simple if (incomplete structure, without THEN and ELSE, no validation is performed)", + "schema": { + "if": { + "properties": { + "foo": { "maximum": 3.0 } + } + } + }, + "tests": [ + { + "description": "if valid", + "data": { "foo": 2.9 }, + "valid": true + }, + { + "description": "if invalid", + "data": { "foo": 3.1 }, + "valid": true + } + ] + }, + { + "description": "Simple if-else (incomplete structure, without THEN, no validation is performed)", + "schema": { + "if": { + "properties": { + "foo": { "minimum": 3.0 } + } + }, + "else": { + "required": [ "bar" ] + } + }, + "tests": [ + { + "description": "if valid, no validation (THEN is not executed because it doesn't exist)", + "data": { "foo": 3.1 }, + "valid": true + }, + { + "description": "if invalid, no validation (ELSE is not executed because THEN doesn't exist)", + "data": { "foo": 2.9 }, + "valid": true + } + ] + }, + { + "description": "Simple if-then (complete structure, without ELSE)", + "schema": { + "if": { + "properties": { + "foo": { "pattern": "^[AB]$" } + } + }, + "then": { + "required": [ "bar" ] + } + }, + "tests": [ + { + "description": "Simple if-then valid", + "data": { "foo": "A", "bar": 1 }, + "valid": true + }, + { + "description": "Simple if-then invalid", + "data": { "foo": "A" }, + "valid": false + } + ] + }, + { + "description": "Simple if-then-else (complete structure)", + "schema": { + "if": { + "properties": { + "foo": { "pattern": "^[AB]$" } + } + }, + "then": { + "required": [ "bar" ] + }, + "else": { + "required": [ "baz" ] + } + }, + "tests": [ + { + "description": "Simple if-then-else, then valid", + "data": { "foo": "A", "bar": 1 }, + "valid": true + }, + { + "description": "Simple if-then-else, then invalid", + "data": { "foo": "A" }, + "valid": false + }, + { + "description": "Simple if-then-else, else valid", + "data": { "foo": "C", "baz": 1 }, + "valid": true + }, + { + "description": "Simple if-then-else, else invalid", + "data": { "foo": "C" }, + "valid": false + } + ] + }, + { + "description": "Nested if-then-else (complete structure)", + "schema": { + "if": { + "properties": { + "foo": { "pattern": "^[AB]$" } + } + }, + "then": { + "if": { + "properties": { + "bar": { "pattern": "^[CD]$" } + } + }, + "then": { + "required": [ "baz" ] + }, + "else": { + "required": [ "qux" ] + } + }, + "else": { + "if": { + "properties": { + "bar": { "pattern": "^[XY]$" } + } + }, + "then": { + "required": [ "baz" ] + }, + "else": { + "required": [ "qux" ] + } + } + }, + "tests": [ + { + "description": "if-then-if-then valid", + "data": { "foo": "A", "bar": "C", "baz": 1 }, + "valid": true + }, + { + "description": "if-then-if-then invalid", + "data": { "foo": "A", "bar": "C" }, + "valid": false + }, + { + "description": "if-then-if-else valid", + "data": { "foo": "A", "bar": "E", "qux": 1 }, + "valid": true + }, + { + "description": "if-then-if-else invalid", + "data": { "foo": "A", "bar": "E" }, + "valid": false + }, + { + "description": "if-else-if-then valid", + "data": { "foo": "C", "bar": "X", "baz": 1 }, + "valid": true + }, + { + "description": "if-else-if-then invalid", + "data": { "foo": "C", "bar": "X" }, + "valid": false + }, + { + "description": "if-else-if-else valid", + "data": { "foo": "C", "bar": "Z", "qux": 1 }, + "valid": true + }, + { + "description": "if-else-if-else invalid", + "data": { "foo": "C", "bar": "Z" }, + "valid": false + } + ] + } +] \ No newline at end of file