diff --git a/core/src/main/java/org/everit/json/schema/ArraySchema.java b/core/src/main/java/org/everit/json/schema/ArraySchema.java index 96ce1f210..bb3a4ba04 100644 --- a/core/src/main/java/org/everit/json/schema/ArraySchema.java +++ b/core/src/main/java/org/everit/json/schema/ArraySchema.java @@ -15,6 +15,8 @@ */ package org.everit.json.schema; +import org.json.JSONArray; + import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -24,8 +26,6 @@ import java.util.function.IntFunction; import java.util.stream.IntStream; -import org.json.JSONArray; - /** * Array schema validator. */ diff --git a/core/src/main/java/org/everit/json/schema/CombinedSchema.java b/core/src/main/java/org/everit/json/schema/CombinedSchema.java index aa64db4f2..eea037b9f 100644 --- a/core/src/main/java/org/everit/json/schema/CombinedSchema.java +++ b/core/src/main/java/org/everit/json/schema/CombinedSchema.java @@ -178,4 +178,17 @@ public void validate(final Object subject) { e.getKeyword()); } } + + @Override + public boolean definesProperty(String field) { + List matching = subschemas.stream() + .filter(schema -> schema.definesProperty(field)) + .collect(Collectors.toList()); + try { + criterion.validate(subschemas.size(), matching.size()); + } catch (ValidationException e) { + return false; + } + return true; + } } diff --git a/core/src/main/java/org/everit/json/schema/NotSchema.java b/core/src/main/java/org/everit/json/schema/NotSchema.java index daf23e3fe..8d314fa4e 100644 --- a/core/src/main/java/org/everit/json/schema/NotSchema.java +++ b/core/src/main/java/org/everit/json/schema/NotSchema.java @@ -61,4 +61,5 @@ public void validate(final Object subject) { } throw new ValidationException(this, "subject must not be valid agains schema " + mustNotMatch); } + } diff --git a/core/src/main/java/org/everit/json/schema/NullSchema.java b/core/src/main/java/org/everit/json/schema/NullSchema.java index 8163dbefd..21ddcbcf7 100644 --- a/core/src/main/java/org/everit/json/schema/NullSchema.java +++ b/core/src/main/java/org/everit/json/schema/NullSchema.java @@ -50,4 +50,5 @@ public void validate(final Object subject) { + subject.getClass().getSimpleName()); } } + } diff --git a/core/src/main/java/org/everit/json/schema/ObjectSchema.java b/core/src/main/java/org/everit/json/schema/ObjectSchema.java index 4508f9cfc..6f83db825 100644 --- a/core/src/main/java/org/everit/json/schema/ObjectSchema.java +++ b/core/src/main/java/org/everit/json/schema/ObjectSchema.java @@ -15,6 +15,10 @@ */ package org.everit.json.schema; +import com.google.common.base.Splitter; +import com.google.common.collect.Lists; +import org.json.JSONObject; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -30,8 +34,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.json.JSONObject; - /** * Object schema validator. */ @@ -392,4 +394,42 @@ public void validate(final Object subject) { } } + @Override + public boolean definesProperty(String field) { + field = field.replaceFirst("^#", "").replaceFirst("^/", ""); + return !field.isEmpty() && (definesPatternProperty(field) + || definesSchemaDependencyProperty(field) + || definesSchemaProperty(field)); + } + + private boolean definesSchemaProperty(String field) { + List fields = Lists.newArrayList(Splitter.on("/").limit(2).split(field)); + String current = unescape(fields.get(0)); + boolean hasSuffix = fields.size() > 1; + if (propertySchemas.containsKey(current)) { + if (hasSuffix) { + String suffix = fields.get(1); + return propertySchemas.get(current).definesProperty(suffix); + } else { + return true; + } + } + return false; + } + + private boolean definesPatternProperty(String field) { + return patternProperties.keySet().stream().filter(pattern -> pattern.matcher(field).matches()) + .findAny().isPresent(); + } + + private boolean definesSchemaDependencyProperty(String field) { + return schemaDependencies.containsKey(field) + || schemaDependencies.values().stream().filter(schema -> schema.definesProperty(field)) + .findAny().isPresent(); + } + + private String unescape(String value) { + return value.replace("~1", "/").replace("~0", "~"); + } + } diff --git a/core/src/main/java/org/everit/json/schema/ReferenceSchema.java b/core/src/main/java/org/everit/json/schema/ReferenceSchema.java index 5c6dba72f..3a43b4d19 100644 --- a/core/src/main/java/org/everit/json/schema/ReferenceSchema.java +++ b/core/src/main/java/org/everit/json/schema/ReferenceSchema.java @@ -61,6 +61,14 @@ public void validate(final Object subject) { referredSchema.validate(subject); } + @Override + public boolean definesProperty(String field) { + if (referredSchema == null) { + throw new IllegalStateException("referredSchema must be injected before validation"); + } + return referredSchema.definesProperty(field); + } + public Schema getReferredSchema() { return referredSchema; } diff --git a/core/src/main/java/org/everit/json/schema/Schema.java b/core/src/main/java/org/everit/json/schema/Schema.java index d0603ad19..1ba6eb163 100644 --- a/core/src/main/java/org/everit/json/schema/Schema.java +++ b/core/src/main/java/org/everit/json/schema/Schema.java @@ -85,6 +85,10 @@ protected Schema(final Builder builder) { */ public abstract void validate(final Object subject); + public boolean definesProperty(final String field) { + return false; + } + @Override public int hashCode() { final int prime = 31; diff --git a/core/src/main/java/org/everit/json/schema/StringSchema.java b/core/src/main/java/org/everit/json/schema/StringSchema.java index a6c6607dd..a021bde09 100644 --- a/core/src/main/java/org/everit/json/schema/StringSchema.java +++ b/core/src/main/java/org/everit/json/schema/StringSchema.java @@ -173,4 +173,5 @@ public void validate(final Object subject) { ValidationException.throwFor(this, rval); } } + } diff --git a/core/src/test/java/org/everit/json/schema/loader/DefinesPropertyTest.java b/core/src/test/java/org/everit/json/schema/loader/DefinesPropertyTest.java new file mode 100644 index 000000000..174227f17 --- /dev/null +++ b/core/src/test/java/org/everit/json/schema/loader/DefinesPropertyTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 Everit Kft. (http://www.everit.org) + * + * 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 org.everit.json.schema.loader; + +import org.everit.json.schema.ObjectSchema; +import org.everit.json.schema.Schema; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.InputStream; + +public class DefinesPropertyTest { + + private static JSONObject ALL_SCHEMAS; + + @BeforeClass + public static void before() { + InputStream stream = DefinesPropertyTest.class.getResourceAsStream( + "/org/everit/jsonvalidator/testschemas.json"); + ALL_SCHEMAS = new JSONObject(new JSONTokener(stream)); + } + + private JSONObject get(final String schemaName) { + return ALL_SCHEMAS.getJSONObject(schemaName); + } + + @Test + public void objectSchemaHasField() { + ObjectSchema actual = (ObjectSchema) SchemaLoader.load(get("pointerResolution")); + Assert.assertTrue(actual.definesProperty("#/rectangle")); + Assert.assertTrue(actual.definesProperty("#/rectangle/a")); + Assert.assertTrue(actual.definesProperty("#/rectangle/b")); + + Assert.assertFalse(actual.definesProperty("#/rectangle/c")); + Assert.assertFalse(actual.definesProperty("#/rectangle/")); + Assert.assertFalse(actual.definesProperty("#/")); + Assert.assertFalse(actual.definesProperty("#/a")); + Assert.assertFalse(actual.definesProperty("#")); + Assert.assertFalse(actual.definesProperty("#/rectangle/a/d")); + } + + @Test + public void recursiveSchemaHasField() { + Schema recursiveSchema = SchemaLoader.load(get("recursiveSchema")); + + Assert.assertTrue(recursiveSchema.definesProperty("#/prop")); + Assert.assertTrue(recursiveSchema.definesProperty("#/prop/subprop")); + Assert.assertTrue(recursiveSchema.definesProperty("#/prop/subprop/subprop")); + Assert.assertTrue(recursiveSchema.definesProperty("#/prop/subprop/subprop/subprop")); + } + + @Test + public void patternPropertiesHasField() { + ObjectSchema actual = (ObjectSchema) SchemaLoader.load(get("patternProperties")); + Assert.assertTrue(actual.definesProperty("#/a")); + Assert.assertTrue(actual.definesProperty("#/aa")); + Assert.assertTrue(actual.definesProperty("#/aaa")); + Assert.assertTrue(actual.definesProperty("#/aaaa")); + Assert.assertTrue(actual.definesProperty("#/aaaaa")); + + Assert.assertFalse(actual.definesProperty("b")); + } + + @Test + public void objectWithSchemaDep() { + ObjectSchema actual = (ObjectSchema) SchemaLoader.load(get("objectWithSchemaDep")); + Assert.assertTrue(actual.definesProperty("#/a")); + Assert.assertTrue(actual.definesProperty("#/b")); + + Assert.assertFalse(actual.definesProperty("#/c")); + } + + @Test + public void objectWithSchemaRectangleDep() { + ObjectSchema actual = (ObjectSchema) SchemaLoader.load(get("objectWithSchemaRectangleDep")); + Assert.assertTrue(actual.definesProperty("#/d")); + Assert.assertTrue(actual.definesProperty("#/rectangle/a")); + Assert.assertTrue(actual.definesProperty("#/rectangle/b")); + + Assert.assertFalse(actual.definesProperty("#/c")); + Assert.assertFalse(actual.definesProperty("#/d/c")); + Assert.assertFalse(actual.definesProperty("#/rectangle/c")); + } + + @Test + public void objectEscape() { + ObjectSchema actual = (ObjectSchema) SchemaLoader.load(get("objectEscape")); + Assert.assertTrue(actual.definesProperty("#/a~0b")); + Assert.assertTrue(actual.definesProperty("#/a~0b/c~1d")); + + Assert.assertFalse(actual.definesProperty("#/a~0b/c/d")); + } + +} diff --git a/core/src/test/resources/org/everit/jsonvalidator/testschemas.json b/core/src/test/resources/org/everit/jsonvalidator/testschemas.json index b04898e4f..7e9ebf624 100644 --- a/core/src/test/resources/org/everit/jsonvalidator/testschemas.json +++ b/core/src/test/resources/org/everit/jsonvalidator/testschemas.json @@ -113,6 +113,30 @@ } } }, + "objectWithSchemaRectangleDep" : { + "type" : "object", + "dependencies" : { + "d" : { + "type" : "object", + "properties" : { + "rectangle" : {"$ref" : "#/definitions/Rectangle" } + } + } + }, + "definitions" : { + "size" : { + "type" : "number", + "minimum" : 0 + }, + "Rectangle" : { + "type" : "object", + "properties" : { + "a" : {"$ref" : "#/definitions/size"}, + "b" : {"$ref" : "#/definitions/size"} + } + } + } + }, "invalidDependency" : { "type" : "object", "dependencies" : { @@ -147,6 +171,20 @@ } } }, + "objectEscape" : { + "type" : "object", + "properties" : { + "a~b" : {"$ref" : "#/definitions/Prop" } + }, + "definitions" : { + "Prop" : { + "type" : "object", + "properties" : { + "c/d" : {"type" : "string"} + } + } + } + }, "pointerResolutionFailure" : { "type" : "object", "properties" : {