diff --git a/pom.xml b/pom.xml index a6efcc515..9a5594091 100644 --- a/pom.xml +++ b/pom.xml @@ -106,6 +106,12 @@ ${version.junit} test + + org.junit.jupiter + junit-jupiter-params + ${version.junit} + test + org.mockito mockito-core diff --git a/src/main/java/com/networknt/schema/IfValidator.java b/src/main/java/com/networknt/schema/IfValidator.java index 85269b390..908df067b 100644 --- a/src/main/java/com/networknt/schema/IfValidator.java +++ b/src/main/java/com/networknt/schema/IfValidator.java @@ -59,10 +59,18 @@ public Set validate(JsonNode node, JsonNode rootNode, String Set errors = new LinkedHashSet(); - Set ifErrors = ifSchema.validate(node, rootNode, at); - if (ifErrors.isEmpty() && thenSchema != null) { + boolean ifConditionPassed; + try { + ifConditionPassed = ifSchema.validate(node, rootNode, at).isEmpty(); + } catch (JsonSchemaException ex) { + // When failFast is enabled, validations are thrown as exceptions. + // An exception means the condition failed + ifConditionPassed = false; + } + + if (ifConditionPassed && thenSchema != null) { errors.addAll(thenSchema.validate(node, rootNode, at)); - } else if (!ifErrors.isEmpty() && elseSchema != null) { + } else if (!ifConditionPassed && elseSchema != null) { errors.addAll(elseSchema.validate(node, rootNode, at)); } diff --git a/src/test/java/com/networknt/schema/Issue366FailFast.java b/src/test/java/com/networknt/schema/Issue366FailFastTest.java similarity index 95% rename from src/test/java/com/networknt/schema/Issue366FailFast.java rename to src/test/java/com/networknt/schema/Issue366FailFastTest.java index 51737e406..051159765 100644 --- a/src/test/java/com/networknt/schema/Issue366FailFast.java +++ b/src/test/java/com/networknt/schema/Issue366FailFastTest.java @@ -13,7 +13,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class Issue366FailFast { +public class Issue366FailFastTest { @BeforeEach public void setup() throws IOException { @@ -35,7 +35,7 @@ private void setupSchema() throws IOException { URI uri = getSchema(); - InputStream in = getClass().getResourceAsStream("/draft7/issue366_schema.json"); + InputStream in = getClass().getResourceAsStream("/schema/issue366_schema.json"); JsonNode testCases = objectMapper.readValue(in, JsonNode.class); this.jsonSchema = schemaFactory.getSchema(uri, testCases,schemaValidatorsConfig); } @@ -75,21 +75,21 @@ public void secondOneValid() throws Exception { @Test public void bothValid() throws Exception { String dataPath = "/data/issue366.json"; - + assertThrows(JsonSchemaException.class, () -> { InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); List testNodes = node.findValues("tests"); JsonNode testNode = testNodes.get(0).get(2); JsonNode dataNode = testNode.get("data"); - jsonSchema.validate(dataNode); + jsonSchema.validate(dataNode); }); } @Test public void neitherValid() throws Exception { String dataPath = "/data/issue366.json"; - + assertThrows(JsonSchemaException.class, () -> { InputStream dataInputStream = getClass().getResourceAsStream(dataPath); JsonNode node = getJsonNodeFromStreamContent(dataInputStream); diff --git a/src/test/java/com/networknt/schema/Issue366FailSlow.java b/src/test/java/com/networknt/schema/Issue366FailSlowTest.java similarity index 99% rename from src/test/java/com/networknt/schema/Issue366FailSlow.java rename to src/test/java/com/networknt/schema/Issue366FailSlowTest.java index 1a8542b9e..3c0d27fae 100644 --- a/src/test/java/com/networknt/schema/Issue366FailSlow.java +++ b/src/test/java/com/networknt/schema/Issue366FailSlowTest.java @@ -13,7 +13,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class Issue366FailSlow { +public class Issue366FailSlowTest { @BeforeEach public void setup() throws IOException { diff --git a/src/test/java/com/networknt/schema/Issue386Test.java b/src/test/java/com/networknt/schema/Issue386Test.java new file mode 100644 index 000000000..8f7bebfdf --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue386Test.java @@ -0,0 +1,75 @@ +package com.networknt.schema; + +import java.io.InputStream; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class Issue386Test { + protected JsonSchema getJsonSchemaFromPathV7(String schemaPath, boolean failFast) { + InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.setFailFast(failFast); + return factory.getSchema(schemaInputStream, config); + } + + protected JsonNode getJsonNodeFromPath(String dataPath) throws Exception { + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(dataInputStream); + return node; + } + + @ParameterizedTest + @ValueSource(booleans = { true, false } ) + public void dataIsValid(boolean failFast) throws Exception { + String schemaPath = "/schema/issue386-v7.json"; + String dataPath = "/data/issue386.json"; + JsonSchema schema = getJsonSchemaFromPathV7(schemaPath, failFast); + JsonNode node = getJsonNodeFromPath(dataPath).get("valid"); + node.forEach(testNode -> { + Set errors = schema.validate(testNode.get("data")); + Assertions.assertEquals(0, errors.size(), "Expected no errors for " + testNode.get("data")); + }); + } + + @Test + public void dataIsInvalidFailFast() throws Exception { + String schemaPath = "/schema/issue386-v7.json"; + String dataPath = "/data/issue386.json"; + JsonSchema schema = getJsonSchemaFromPathV7(schemaPath, true); + JsonNode node = getJsonNodeFromPath(dataPath).get("invalid"); + node.forEach(testNode -> { + try { + schema.validate(testNode.get("data")); + Assertions.fail(); + } catch (JsonSchemaException e) { + Assertions.assertEquals(testNode.get("expectedErrors").get(0).asText(), e.getMessage()); + } + }); + } + + @Test + public void dataIsInvalidFailSlow() throws Exception { + String schemaPath = "/schema/issue386-v7.json"; + String dataPath = "/data/issue386.json"; + JsonSchema schema = getJsonSchemaFromPathV7(schemaPath, false); + JsonNode node = getJsonNodeFromPath(dataPath).get("invalid"); + node.forEach(testNode -> { + Set errors = schema.validate(testNode.get("data")); + List errorMessages = errors.stream().map(x -> x.getMessage()).collect(Collectors.toList()); + testNode.get("expectedErrors").forEach(expectedError -> { + Assertions.assertTrue(errorMessages.contains(expectedError.asText())); + }); + }); + } +} diff --git a/src/test/resources/data/issue386.json b/src/test/resources/data/issue386.json new file mode 100644 index 000000000..a70a95066 --- /dev/null +++ b/src/test/resources/data/issue386.json @@ -0,0 +1,49 @@ +{ + "valid": [ + { + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "country": "United States of America", + "postal_code": "20500" + } + }, + { + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "20500" + } + }, + { + "data": { + "street_address": "24 Sussex Drive", + "country": "Canada", + "postal_code": "K1M 1M4" + } + }, + { + "data": { + "street_address": "Adriaan Goekooplaan", + "country": "Netherlands", + "postal_code": "2517 JX" + } + } + ], + "invalid": [ + { + "data": { + "street_address": "24 Sussex Drive", + "country": "Canada", + "postal_code": "10000" + }, + "expectedErrors": ["$.postal_code: does not match the regex pattern [A-Z][0-9][A-Z] [0-9][A-Z][0-9]"] + }, + { + "description": "invalid through first then", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "K1M 1M4" + }, + "expectedErrors": ["$.postal_code: does not match the regex pattern [0-9]{5}(-[0-9]{4})?"] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/draft2019-09/if-then-else.json b/src/test/resources/draft2019-09/if-then-else.json index 1cdeeb9e1..7b014aeac 100644 --- a/src/test/resources/draft2019-09/if-then-else.json +++ b/src/test/resources/draft2019-09/if-then-else.json @@ -184,5 +184,183 @@ "valid": true } ] + }, + { + "description": "conditions by properties", + "schema": { + "type": "object", + "properties": { + "street_address": { + "type": "string" + }, + "country": { + "enum": ["United States of America", "Canada"] + } + }, + "if": { + "properties": { + "country": { + "const": "United States of America" + } + } + }, + "then": { + "properties": { + "postal_code": { + "pattern": "[0-9]{5}(-[0-9]{4})?" + } + } + }, + "else": { + "properties": { + "postal_code": { + "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" + } + } + } + }, + "tests": [ + { + "description": "valid through then", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "country": "United States of America", + "postal_code": "20500" + }, + "valid": true + }, + { + "description": "valid through then, alternative match", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "20500" + }, + "valid": true + }, + { + "description": "valid through else", + "data": { + "street_address": "24 Sussex Drive", + "country": "Canada", + "postal_code": "K1M 1M4" + }, + "valid": true + }, + { + "description": "invalid through else", + "data": { + "street_address": "24 Sussex Drive", + "country": "Canada", + "postal_code": "10000" + }, + "valid": false + } + , + { + "description": "invalid through then", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "K1M 1M4" + }, + "valid": false + } + ] + }, + { + "description": "conditions by allOf properties", + "schema": { + "type": "object", + "properties": { + "street_address": { + "type": "string" + }, + "country": { + "default": "United States of America", + "enum": ["United States of America", "Canada", "Netherlands"] + } + }, + "allOf": [ + { + "if": { + "properties": { "country": { "const": "United States of America" } } + }, + "then": { + "properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } } + } + }, + { + "if": { + "properties": { "country": { "const": "Canada" } }, + "required": ["country"] + }, + "then": { + "properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } } + } + }, + { + "if": { + "properties": { "country": { "const": "Netherlands" } }, + "required": ["country"] + }, + "then": { + "properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } } + } + } + ] + }, + "tests": [ + { + "description": "valid first if", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "country": "United States of America", + "postal_code": "20500" + }, + "valid": true + }, + { + "description": "valid first if, alternative match", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "20500" + }, + "valid": true + }, + { + "description": "valid second if", + "data": { + "street_address": "24 Sussex Drive", + "country": "Canada", + "postal_code": "K1M 1M4" + }, + "valid": true + }, + { + "description": "valid third if", + "data": { + "street_address": "Adriaan Goekooplaan", + "country": "Netherlands", + "postal_code": "2517 JX" + }, + "valid": true + }, + { + "description": "invalid through second then", + "data": { + "street_address": "24 Sussex Drive", + "country": "Canada", + "postal_code": "10000" + }, + "valid": false + }, + { + "description": "invalid through first then", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "K1M 1M4" + }, + "valid": false + } + ] } ] diff --git a/src/test/resources/draft7/if-then-else.json b/src/test/resources/draft7/if-then-else.json index 634aa0664..7b014aeac 100644 --- a/src/test/resources/draft7/if-then-else.json +++ b/src/test/resources/draft7/if-then-else.json @@ -198,24 +198,24 @@ } }, "if": { - "properties": { - "country": { - "const": "United States of America" - } + "properties": { + "country": { + "const": "United States of America" + } } }, "then": { - "properties": { - "postal_code": { - "pattern": "[0-9]{5}(-[0-9]{4})?" - } + "properties": { + "postal_code": { + "pattern": "[0-9]{5}(-[0-9]{4})?" + } } }, "else": { - "properties": { - "postal_code": { - "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" - } + "properties": { + "postal_code": { + "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" + } } } }, @@ -229,6 +229,14 @@ }, "valid": true }, + { + "description": "valid through then, alternative match", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "20500" + }, + "valid": true + }, { "description": "valid through else", "data": { @@ -239,7 +247,7 @@ "valid": true }, { - "description": "invalid", + "description": "invalid through else", "data": { "street_address": "24 Sussex Drive", "country": "Canada", @@ -247,6 +255,112 @@ }, "valid": false } + , + { + "description": "invalid through then", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "K1M 1M4" + }, + "valid": false + } + ] + }, + { + "description": "conditions by allOf properties", + "schema": { + "type": "object", + "properties": { + "street_address": { + "type": "string" + }, + "country": { + "default": "United States of America", + "enum": ["United States of America", "Canada", "Netherlands"] + } + }, + "allOf": [ + { + "if": { + "properties": { "country": { "const": "United States of America" } } + }, + "then": { + "properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } } + } + }, + { + "if": { + "properties": { "country": { "const": "Canada" } }, + "required": ["country"] + }, + "then": { + "properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } } + } + }, + { + "if": { + "properties": { "country": { "const": "Netherlands" } }, + "required": ["country"] + }, + "then": { + "properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } } + } + } + ] + }, + "tests": [ + { + "description": "valid first if", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "country": "United States of America", + "postal_code": "20500" + }, + "valid": true + }, + { + "description": "valid first if, alternative match", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "20500" + }, + "valid": true + }, + { + "description": "valid second if", + "data": { + "street_address": "24 Sussex Drive", + "country": "Canada", + "postal_code": "K1M 1M4" + }, + "valid": true + }, + { + "description": "valid third if", + "data": { + "street_address": "Adriaan Goekooplaan", + "country": "Netherlands", + "postal_code": "2517 JX" + }, + "valid": true + }, + { + "description": "invalid through second then", + "data": { + "street_address": "24 Sussex Drive", + "country": "Canada", + "postal_code": "10000" + }, + "valid": false + }, + { + "description": "invalid through first then", + "data": { + "street_address": "1600 Pennsylvania Avenue NW", + "postal_code": "K1M 1M4" + }, + "valid": false + } ] } ] diff --git a/src/test/resources/schema/issue386-v7.json b/src/test/resources/schema/issue386-v7.json new file mode 100644 index 000000000..a63d0b649 --- /dev/null +++ b/src/test/resources/schema/issue386-v7.json @@ -0,0 +1,40 @@ +{ + "type": "object", + "properties": { + "street_address": { + "type": "string" + }, + "country": { + "default": "United States of America", + "enum": ["United States of America", "Canada", "Netherlands"] + } + }, + "allOf": [ + { + "if": { + "properties": { "country": { "const": "United States of America" } } + }, + "then": { + "properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } } + } + }, + { + "if": { + "properties": { "country": { "const": "Canada" } }, + "required": ["country"] + }, + "then": { + "properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } } + } + }, + { + "if": { + "properties": { "country": { "const": "Netherlands" } }, + "required": ["country"] + }, + "then": { + "properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } } + } + } + ] +} \ No newline at end of file