diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c4a831b4a..be0f72403 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,3 +1,7 @@ +### Pablo Pinhero + +* Add a "lib" target to the build: jar with all dependencies builtin. + ### Corey Sciuto * Tests for date-time format attribute; date-time-millis format attribute (now diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 58ffbf555..857d9a671 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,75 +1,30 @@ -### 2.1.10 +### 2.2.5 -* Plug in hyperschema syntax support... -* Gradle 1.11. -* Add a main method. -* -core 1.1.11. +* Fix issue #102: detect, and fail on, circular validation. +* Simplify ValidationProcessor. +* Remove one-jar generation; the -lib jar now includes Main-Class. -### 2.1.9 +### 2.2.4 -* Fix bug with string length calculations: it is the number of Unicode code - points which matters, not the number of `char`s (issue #92). -* Depend on -core 1.1.10: schema sources with trailing input are now considered - illegal. -* Make all tests run from the command line. -* Small javadoc fixes. +* Add a "lib" target to the build. +* Issue #99: append syntax errors when throwing an InvalidSchemaException. +* Issue #100: attempt to load resources from the context classloader if loading + from JsonLoader.class fails. -### 2.1.8 +### 2.2.3 -* Add "deep validation": validate children even if container fails -* -core update to 1.1.9: package changes (BREAKS OLDER CODE) -* Change licensing to dual LGPLv3/ ASL 2.0 -* Dependencies updates (Joda Time 2.3, libphonenumber 5.9) +* Re-release... 2.2.2 was compiled with JDK 8 :/ -### 2.1.7 +### 2.2.2 -* Import all format attributes from - [json-schema-formats](https://github.com/fge/json-schema-formats). -* Switch to gradle for build. -* Major dependencies updates; drop ServiceLoader for message bundles. -* Fix javadoc generation. +* Depend on -core 1.2.1 to work around Rhino bug with other packages sealing the + context. -### 2.1.6 +### 2.2.1 -* Update json-schema-core dependency to 1.1.7. -* Fix a bug in pom.xml which would cause the service file to not be generated. -* Fix two places where core messages would not be fetched properly. +* Main now uses current working directory as default URI namespace. -### 2.1.5 +### 2.2.0 -* Update json-schema-core dependency to 1.1.6. -* Use [msg-simple](https://github.com/fge/msg-simple) for message bundles. -* Remove all unchecked exceptions, now unneeded. -* Improve, test all error messages. -* pom.xml improvements. -* Remove obsolete code. - -### 2.1.4 - -* Update -core dependency. -* Use resource bundles for all configuration/validation messages (issue #55). -* Modify pom.xml to allow OSGi-capable deployments (courtesy of Matt Bishop). - -### 2.1.3 - -* Modify date/time format checkings to accurately check for the required number - of digits - -### 2.1.2 - -* Update -core dependency to 1.1.3 -* Update libphonenumber dependency - -### 2.1.1 - -* Update -core dependency (including Guava), adapt code. -* Some error message rework. - -### 2.1.0 - -* Depend on -core 1.1.1. Change relevant code accordingly. -* Simplify failure code on syntax validation failure. -* Fix `date-time` format checking: up to three millisecond digits are allowed by -* ISO 8601. -* Joda Time dependency updated to 2.2. +* New major release. diff --git a/build.gradle b/build.gradle index cc23d4051..d8c7b3b89 100644 --- a/build.gradle +++ b/build.gradle @@ -35,27 +35,6 @@ * * https://github.com/spring-projects/gradle-plugins/tree/master/propdeps-plugin */ -buildscript { - repositories { - mavenCentral(); -// maven { -// url "http://repo.springsource.org/plugins-release"; -// } - } - dependencies { - classpath(group: "com.github.rholder", name: "gradle-one-jar", - version: "1.0.4"); -// classpath(group: "org.springframework.build.gradle", -// name: "propdeps-plugin", version: "0.0.5"); - } -}; - -//configure(allprojects) { -// apply(plugin: "propdeps"); -// apply(plugin: "propdeps-maven"); -// apply(plugin: "propdeps-idea"); -// apply(plugin: "propdeps-eclipse"); -//} apply(plugin: "java"); apply(plugin: "maven"); @@ -63,9 +42,15 @@ apply(plugin: "signing"); apply(plugin: "osgi"); apply(plugin: "idea"); apply(plugin: "eclipse"); -apply(plugin: "gradle-one-jar"); -apply(from: "project.gradle"); +group = "com.github.fge"; +version = "2.2.5"; +sourceCompatibility = "1.6"; +targetCompatibility = "1.6"; // defaults to sourceCompatibility + +project.ext { + description = "A Java implementation of the JSON Schema specification"; +}; /* * Repositories to use @@ -74,6 +59,40 @@ repositories { mavenCentral(); } +/* + * List of dependencies + */ +dependencies { + compile(group: "com.github.fge", name: "json-schema-core", + version: "1.2.4"); + compile(group: "javax.mail", name: "mailapi", version: "1.4.3"); + compile(group: "joda-time", name: "joda-time", version: "2.3"); + compile(group: "com.googlecode.libphonenumber", name: "libphonenumber", + version: "6.0"); + compile(group: "com.google.code.findbugs", name: "jsr305", + version: "2.0.1"); + compile(group: "net.sf.jopt-simple", name: "jopt-simple", version: "4.6"); + testCompile(group: "org.testng", name: "testng", version: "6.8.7") { + exclude(group: "junit", module: "junit"); + exclude(group: "org.beanshell", module: "bsh"); + exclude(group: "org.yaml", module: "snakeyaml"); + }; + testCompile(group: "org.mockito", name: "mockito-core", version: "1.9.5"); + testCompile(group: "org.easytesting", name: "fest-assert", version: "1.4"); +} + +javadoc.options.links("http://docs.oracle.com/javase/6/docs/api/"); +javadoc.options.links("http://jsr-305.googlecode.com/svn/trunk/javadoc/"); +javadoc.options.links("http://fasterxml.github.com/jackson-databind/javadoc/2.2.0/"); +javadoc.options.links("http://fasterxml.github.com/jackson-core/javadoc/2.2.0/"); +javadoc.options.links("http://docs.guava-libraries.googlecode.com/git-history/v16.0.1/javadoc/"); +javadoc.options.links("http://fge.github.io/btf/"); +javadoc.options.links("http://fge.github.io/msg-simple/"); +javadoc.options.links("http://fge.github.io/jackson-coreutils/"); +javadoc.options.links("http://fge.github.io/uri-template/"); +javadoc.options.links("http://fge.github.io/json-schema-core/1.2.x/"); + + /* * Necessary! Otherwise TestNG will not be used... * @@ -123,16 +142,28 @@ task javadocJar(type: Jar, dependsOn: copyDocFiles) { from javadoc.destinationDir; } -task fullJar(type: OneJar) { - mainClass = "com.github.fge.jsonschema.main.cli.Main"; - archiveName = "jsonschema.jar"; +/* + * Creates a jar that can be used as a library on java projects. + * This jar already includes all the dependencies. + */ +task libJar(type: Jar, dependsOn: jar) { + classifier = "lib"; + from { + configurations.compile.collect { zipTree(it) } + }; + with jar; } +jar { + manifest { + attributes("Main-Class": "com.github.fge.jsonschema.main.cli.Main"); + } +} artifacts { archives jar; archives sourcesJar; archives javadocJar; - archives fullJar; + archives libJar; } task wrapper(type: Wrapper) { diff --git a/project.gradle b/project.gradle deleted file mode 100644 index c74d710aa..000000000 --- a/project.gradle +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) - * - * This software is dual-licensed under: - * - * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any - * later version; - * - the Apache Software License (ASL) version 2.0. - * - * The text of this file and of both licenses is available at the root of this - * project or, if you have the jar distribution, in directory META-INF/, under - * the names LGPL-3.0.txt and ASL-2.0.txt respectively. - * - * Direct link to the sources: - * - * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt - * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt - */ - -/* - * Project-specific settings. Unfortunately we cannot put the name in there! - */ -group = "com.github.fge"; -version = "2.2.0"; -sourceCompatibility = "1.6"; -targetCompatibility = "1.6"; // defaults to sourceCompatibility - -project.ext { - description = "A Java implementation of the JSON Schema specification"; -}; - -/* - * List of dependencies - */ -dependencies { - compile(group: "com.github.fge", name: "json-schema-core", - version: "1.2.0"); - compile(group: "javax.mail", name: "mailapi", version: "1.4.3"); - compile(group: "joda-time", name: "joda-time", version: "2.3"); - compile(group: "com.googlecode.libphonenumber", name: "libphonenumber", - version: "6.0"); - compile(group: "com.google.code.findbugs", name: "jsr305", - version: "2.0.1"); - compile(group: "net.sf.jopt-simple", name: "jopt-simple", version: "4.6"); - testCompile(group: "org.testng", name: "testng", version: "6.8.7") { - exclude(group: "junit", module: "junit"); - exclude(group: "org.beanshell", module: "bsh"); - exclude(group: "org.yaml", module: "snakeyaml"); - }; - testCompile(group: "org.mockito", name: "mockito-core", version: "1.9.5"); - testCompile(group: "org.easytesting", name: "fest-assert", version: "1.4"); -} - -javadoc.options.links("http://docs.oracle.com/javase/6/docs/api/"); -javadoc.options.links("http://jsr-305.googlecode.com/svn/trunk/javadoc/"); -javadoc.options.links("http://fasterxml.github.com/jackson-databind/javadoc/2.2.0/"); -javadoc.options.links("http://fasterxml.github.com/jackson-core/javadoc/2.2.0/"); -javadoc.options.links("http://docs.guava-libraries.googlecode.com/git-history/v16.0.1/javadoc/"); -javadoc.options.links("http://fge.github.io/btf/"); -javadoc.options.links("http://fge.github.io/msg-simple/"); -javadoc.options.links("http://fge.github.io/jackson-coreutils/"); -javadoc.options.links("http://fge.github.io/uri-template/"); -javadoc.options.links("http://fge.github.io/json-schema-core/1.2.x/"); - diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 4c0aacfa0..000000000 --- a/settings.gradle +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) - * - * This software is dual-licensed under: - * - * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any - * later version; - * - the Apache Software License (ASL) version 2.0. - * - * The text of this file and of both licenses is available at the root of this - * project or, if you have the jar distribution, in directory META-INF/, under - * the names LGPL-3.0.txt and ASL-2.0.txt respectively. - * - * Direct link to the sources: - * - * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt - * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt - */ - -rootProject.name = "json-schema-validator"; diff --git a/src/main/java/com/github/fge/jsonschema/main/JsonSchemaFactory.java b/src/main/java/com/github/fge/jsonschema/main/JsonSchemaFactory.java index 2bf046c62..bfaca5fe0 100644 --- a/src/main/java/com/github/fge/jsonschema/main/JsonSchemaFactory.java +++ b/src/main/java/com/github/fge/jsonschema/main/JsonSchemaFactory.java @@ -30,6 +30,7 @@ import com.github.fge.jsonschema.core.load.SchemaLoader; import com.github.fge.jsonschema.core.load.configuration.LoadingConfiguration; import com.github.fge.jsonschema.core.messages.JsonSchemaCoreMessageBundle; +import com.github.fge.jsonschema.core.processing.CachingProcessor; import com.github.fge.jsonschema.core.processing.Processor; import com.github.fge.jsonschema.core.processing.ProcessorMap; import com.github.fge.jsonschema.core.ref.JsonRef; @@ -40,6 +41,7 @@ import com.github.fge.jsonschema.processors.data.SchemaContext; import com.github.fge.jsonschema.processors.data.ValidatorList; import com.github.fge.jsonschema.processors.syntax.SyntaxValidator; +import com.github.fge.jsonschema.processors.validation.SchemaContextEquivalence; import com.github.fge.jsonschema.processors.validation.ValidationChain; import com.github.fge.jsonschema.processors.validation.ValidationProcessor; import com.github.fge.msgsimple.bundle.MessageBundle; @@ -270,6 +272,9 @@ private Processor buildProcessor() map.addEntry(ref, chain); } - return map.getProcessor(); + final Processor processor + = map.getProcessor(); + return new CachingProcessor(processor, + SchemaContextEquivalence.getInstance()); } } diff --git a/src/main/java/com/github/fge/jsonschema/main/cli/CustomHelpFormatter.java b/src/main/java/com/github/fge/jsonschema/main/cli/CustomHelpFormatter.java index e9577f33c..95c6eaa4a 100644 --- a/src/main/java/com/github/fge/jsonschema/main/cli/CustomHelpFormatter.java +++ b/src/main/java/com/github/fge/jsonschema/main/cli/CustomHelpFormatter.java @@ -20,6 +20,7 @@ package com.github.fge.jsonschema.main.cli; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import joptsimple.HelpFormatter; import joptsimple.OptionDescriptor; @@ -33,8 +34,30 @@ final class CustomHelpFormatter implements HelpFormatter { - private static final String HELP_PREAMBLE - = "Syntax: java -jar jsonschema.jar [options] file [file...]"; + private static final List HELP_PREAMBLE = ImmutableList.of( + "Syntax:", + " java -jar jsonschema.jar [options] schema file [file...]", + " java -jar jsonschema.jar --syntax [options] schema [schema...]", + "", + "Options: " + ); + + private static final List HELP_POST + = ImmutableList.builder() + .add("") + .add("Exit codes:") + .add(" 0: validation successful;") + .add(" 1: exception occurred (appears on stderr)") + .add(" 2: command line syntax error (missing argument, etc)") + .add(" 100: one or more file(s) failed validation") + .add(" 101: one or more schema(s) is/are invalid") + .add("") + .add("Note: by default, the URI of schemas you use in validation mode") + .add("(that is, when you don't use --syntax) is considered to be the") + .add("current working directory plus the filename. If your schemas") + .add("all have a common URI prefix in a top level \"id\", you can fake") + .add("that the current directory is that prefix using --fakeroot.") + .build(); private static final String LINE_SEPARATOR = System.getProperty("line.separator", "\n"); @@ -50,9 +73,7 @@ public String format(final Map options) final Set opts = new LinkedHashSet( options.values()); - lines.add(HELP_PREAMBLE); - lines.add(""); - lines.add("Options: "); + lines.addAll(HELP_PREAMBLE); final int helpIndex = lines.size(); StringBuilder sb; @@ -61,7 +82,7 @@ public String format(final Map options) if (descriptor.representsNonOptions()) continue; final Collection names = descriptor.options(); - sb = new StringBuilder().append('\t') + sb = new StringBuilder().append(" ") .append(optionsToString(names)); if (descriptor.requiresArgument()) sb.append(" uri"); @@ -72,13 +93,7 @@ public String format(final Map options) lines.add(sb.toString()); } - lines.add(""); - lines.add("Exit codes:"); - lines.add("\t0: validation successful;"); - lines.add("\t1: exception occurred (appears on stderr)"); - lines.add("\t2: command line syntax error (missing argument, etc)"); - lines.add("\t100: one or more file(s) failed validation"); - lines.add("\t101: one or more schema(s) is/are invalid"); + lines.addAll(HELP_POST); return JOINER.join(lines) + LINE_SEPARATOR; } diff --git a/src/main/java/com/github/fge/jsonschema/main/cli/Main.java b/src/main/java/com/github/fge/jsonschema/main/cli/Main.java index 24cee3992..4a9e2b37f 100644 --- a/src/main/java/com/github/fge/jsonschema/main/cli/Main.java +++ b/src/main/java/com/github/fge/jsonschema/main/cli/Main.java @@ -132,7 +132,8 @@ else if (optionSet.has("quiet")) { throws IOException { final URITranslatorConfigurationBuilder builder - = URITranslatorConfiguration.newBuilder(); + = URITranslatorConfiguration.newBuilder() + .setNamespace(getCwd()); if (fakeRoot != null) builder.addPathRedirect(fakeRoot, getCwd()); final LoadingConfiguration cfg = LoadingConfiguration.newBuilder() @@ -175,6 +176,7 @@ private RetCode doValidation(final Reporter reporter, throws IOException, ProcessingException { final File schemaFile = files.remove(0); + final String uri = schemaFile.toURI().normalize().toString(); JsonNode node; node = MAPPER.readTree(schemaFile); @@ -183,7 +185,7 @@ private RetCode doValidation(final Reporter reporter, return SCHEMA_SYNTAX_ERROR; } - final JsonSchema schema = factory.getJsonSchema(node); + final JsonSchema schema = factory.getJsonSchema(uri); RetCode ret = ALL_OK, retcode; diff --git a/src/main/java/com/github/fge/jsonschema/processors/data/FullData.java b/src/main/java/com/github/fge/jsonschema/processors/data/FullData.java index f3d6cd6d0..d30c02434 100644 --- a/src/main/java/com/github/fge/jsonschema/processors/data/FullData.java +++ b/src/main/java/com/github/fge/jsonschema/processors/data/FullData.java @@ -61,6 +61,7 @@ public FullData(final SchemaTree schema, final JsonTree instance) this(schema, instance, false); } + @Deprecated public FullData(final SchemaTree schema) { this(schema, null); diff --git a/src/main/java/com/github/fge/jsonschema/processors/syntax/SyntaxValidator.java b/src/main/java/com/github/fge/jsonschema/processors/syntax/SyntaxValidator.java index 784b6238a..d3a4100df 100644 --- a/src/main/java/com/github/fge/jsonschema/processors/syntax/SyntaxValidator.java +++ b/src/main/java/com/github/fge/jsonschema/processors/syntax/SyntaxValidator.java @@ -32,6 +32,7 @@ import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.core.tree.CanonicalSchemaTree; import com.github.fge.jsonschema.core.tree.SchemaTree; +import com.github.fge.jsonschema.core.tree.key.SchemaKey; import com.github.fge.jsonschema.core.util.Dictionary; import com.github.fge.jsonschema.core.util.ValueHolder; import com.github.fge.jsonschema.library.Library; @@ -147,6 +148,6 @@ private ProcessingResult> getResult(final JsonNode schem private static ValueHolder holder(final JsonNode node) { return ValueHolder.hold("schema", - new CanonicalSchemaTree(node)); + new CanonicalSchemaTree(SchemaKey.anonymousKey(), node)); } } diff --git a/src/main/java/com/github/fge/jsonschema/processors/validation/InstanceValidator.java b/src/main/java/com/github/fge/jsonschema/processors/validation/InstanceValidator.java new file mode 100644 index 000000000..81ef71750 --- /dev/null +++ b/src/main/java/com/github/fge/jsonschema/processors/validation/InstanceValidator.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) + * + * This software is dual-licensed under: + * + * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any + * later version; + * - the Apache Software License (ASL) version 2.0. + * + * The text of this file and of both licenses is available at the root of this + * project or, if you have the jar distribution, in directory META-INF/, under + * the names LGPL-3.0.txt and ASL-2.0.txt respectively. + * + * Direct link to the sources: + * + * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt + * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt + */ + +package com.github.fge.jsonschema.processors.validation; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.github.fge.jackson.JacksonUtils; +import com.github.fge.jackson.jsonpointer.JsonPointer; +import com.github.fge.jsonschema.core.exceptions.InvalidSchemaException; +import com.github.fge.jsonschema.core.exceptions.ProcessingException; +import com.github.fge.jsonschema.core.processing.Processor; +import com.github.fge.jsonschema.core.report.ProcessingMessage; +import com.github.fge.jsonschema.core.report.ProcessingReport; +import com.github.fge.jsonschema.core.tree.JsonTree; +import com.github.fge.jsonschema.core.tree.SchemaTree; +import com.github.fge.jsonschema.keyword.validator.KeywordValidator; +import com.github.fge.jsonschema.main.JsonSchema; +import com.github.fge.jsonschema.main.JsonValidator; +import com.github.fge.jsonschema.processors.data.FullData; +import com.github.fge.jsonschema.processors.data.SchemaContext; +import com.github.fge.jsonschema.processors.data.ValidatorList; +import com.github.fge.msgsimple.bundle.MessageBundle; +import com.github.fge.uritemplate.URITemplate; +import com.github.fge.uritemplate.URITemplateException; +import com.github.fge.uritemplate.URITemplateParseException; +import com.github.fge.uritemplate.vars.VariableMap; +import com.google.common.base.Equivalence; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.concurrent.NotThreadSafe; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Processor for validating one schema/instance pair + * + *

One such processor is created for each schema/instance validation.

+ * + *

Internally, all validation operations provided by the API (whether that + * be a {@link JsonSchema}, via {@link JsonValidator} or using {@link + * ValidationProcessor} directly) will eventually instantiate one of these. More + * precisely, this is instantiated by {@link + * ValidationProcessor#process(ProcessingReport, FullData)}.

+ * + */ +@NotThreadSafe +@ParametersAreNonnullByDefault +public final class InstanceValidator + implements Processor +{ + private final MessageBundle syntaxMessages; + private final MessageBundle validationMessages; + private final Processor keywordBuilder; + + /* + * It is possible to trigger a validation loop if there is a repeated + * triplet schema ID/schema pointer/instance pointer while we validate; + * example schema: + * + * { "oneOf": [ {}, { "$ref": "#" } ] } + * + * Whatever the data, validation will end up validating, for a same pointer + * into the instance, the following pointers into the schema: + * + * "" -> "/oneOf/0" -> "/oneOf/1" -> "/oneOf/0" <-- LOOP + * + * This is not a JSON Reference loop here, but truly a validation loop. + * + * We therefore use this set to record the triplets seen by using an + * Equivalence over FullData which detects this. This is helped by the fact + * that SchemaTree now implements equals()/hashCode() in -core; since this + * class is instantiated for each instance validation, we are certain that + * what instance pointer is seen is the one of the instance we validate. + */ + private final Set> visited + = Sets.newLinkedHashSet(); + + /** + * Constructor -- do not use directly! + * + * @param syntaxMessages the syntax message bundle + * @param validationMessages the validation message bundle + * @param keywordBuilder the keyword builder + */ + public InstanceValidator(final MessageBundle syntaxMessages, + final MessageBundle validationMessages, + final Processor keywordBuilder) + { + this.syntaxMessages = syntaxMessages; + this.validationMessages = validationMessages; + this.keywordBuilder = keywordBuilder; + } + + @Override + public FullData process(final ProcessingReport report, + final FullData input) + throws ProcessingException + { + /* + * We don't want the same validation context to appear twice, see above + */ + if (!visited.add(FULL_DATA_EQUIVALENCE.wrap(input))) + throw new ProcessingException(validationLoopMessage(input)); + + + /* + * Build a validation context, attach a report to it + */ + final SchemaContext context = new SchemaContext(input); + + /* + * Get the full context from the cache. Inject the messages into the + * main report. + */ + final ValidatorList fullContext = keywordBuilder.process(report, + context); + + if (fullContext == null) { + final ProcessingMessage message = collectSyntaxErrors(report); + throw new InvalidSchemaException(message); + } + + /* + * Get the calculated context. Build the data. + */ + final SchemaContext newContext = fullContext.getContext(); + final FullData data = new FullData(newContext.getSchema(), + input.getInstance(), input.isDeepCheck()); + + /* + * Validate against all keywords. + */ + for (final KeywordValidator validator: fullContext) + validator.validate(this, report, validationMessages, data); + + /* + * At that point, if the report is a failure, we quit: there is no + * reason to go any further. Unless the user has asked to continue even + * in this case. + */ + if (!(report.isSuccess() || data.isDeepCheck())) + return input; + + /* + * Now check whether this is a container node with a size greater than + * 0. If not, no need to go see the children. + */ + final JsonNode node = data.getInstance().getNode(); + if (node.size() == 0) + return input; + + if (node.isArray()) + processArray(report, data); + else + processObject(report, data); + + return input; + } + + @Override + public String toString() + { + return "instance validator"; + } + + private void processArray(final ProcessingReport report, + final FullData input) + throws ProcessingException + { + final SchemaTree tree = input.getSchema(); + final JsonTree instance = input.getInstance(); + + final JsonNode schema = tree.getNode(); + final JsonNode node = instance.getNode(); + + final JsonNode digest = ArraySchemaDigester.getInstance() + .digest(schema); + final ArraySchemaSelector selector = new ArraySchemaSelector(digest); + + final int size = node.size(); + + FullData data; + JsonTree newInstance; + + for (int index = 0; index < size; index++) { + newInstance = instance.append(JsonPointer.of(index)); + data = input.withInstance(newInstance); + for (final JsonPointer ptr: selector.selectSchemas(index)) { + data = data.withSchema(tree.append(ptr)); + process(report, data); + } + } + } + + private void processObject(final ProcessingReport report, + final FullData input) + throws ProcessingException + { + final SchemaTree tree = input.getSchema(); + final JsonTree instance = input.getInstance(); + + final JsonNode schema = tree.getNode(); + final JsonNode node = instance.getNode(); + + final JsonNode digest = ObjectSchemaDigester.getInstance() + .digest(schema); + final ObjectSchemaSelector selector = new ObjectSchemaSelector(digest); + + final List fields = Lists.newArrayList(node.fieldNames()); + Collections.sort(fields); + + FullData data; + JsonTree newInstance; + + for (final String field: fields) { + newInstance = instance.append(JsonPointer.of(field)); + data = input.withInstance(newInstance); + for (final JsonPointer ptr: selector.selectSchemas(field)) { + data = data.withSchema(tree.append(ptr)); + process(report, data); + } + } + } + + private ProcessingMessage validationLoopMessage(final FullData input) + { + final String errmsg + = validationMessages.getMessage("err.common.validationLoop"); + final ArrayNode node = JacksonUtils.nodeFactory().arrayNode(); + for (final Equivalence.Wrapper e: visited) + //noinspection ConstantConditions + node.add(toJson(e.get())); + return input.newMessage() + .put("domain", "validation") + .setMessage(errmsg) + .putArgument("alreadyVisited", toJson(input)) + .putArgument("instancePointer", + input.getInstance().getPointer().toString()) + .put("validationPath", node); + } + + private ProcessingMessage collectSyntaxErrors(final ProcessingReport report) + { + /* + * OK, that's for issue #99 but that's ugly nevertheless. + * + * We want syntax error messages to appear in the exception text. + */ + final String msg = syntaxMessages.getMessage("core.invalidSchema"); + final ArrayNode arrayNode = JacksonUtils.nodeFactory().arrayNode(); + JsonNode node; + for (final ProcessingMessage message: report) { + node = message.asJson(); + if ("syntax".equals(node.path("domain").asText())) + arrayNode.add(node); + } + final StringBuilder sb = new StringBuilder(msg); + sb.append("\nSyntax errors:\n"); + sb.append(JacksonUtils.prettyPrint(arrayNode)); + return new ProcessingMessage().setMessage(sb.toString()); + } + + private static String toJson(final FullData data) + { + final SchemaTree tree = data.getSchema(); + final URI baseUri = tree.getLoadingRef().getLocator(); + final VariableMap vars = VariableMap.newBuilder().addScalarValue("ptr", + tree.getPointer()).freeze(); + // TODO: there should be an easier way to do that... + final URITemplate template; + try { + template = new URITemplate(baseUri + "{+ptr}"); + } catch (URITemplateParseException e) { + throw new IllegalStateException("wtf??", e); + } + try { + return template.toString(vars); + } catch (URITemplateException e) { + throw new IllegalStateException("wtf??", e); + } + } + + @ParametersAreNonnullByDefault + private static final Equivalence FULL_DATA_EQUIVALENCE + = new Equivalence() + { + @Override + protected boolean doEquivalent(final FullData a, final FullData b) + { + final JsonPointer ptra = a.getInstance().getPointer(); + final JsonPointer ptrb = b.getInstance().getPointer(); + return a.getSchema().equals(b.getSchema()) + && ptra.equals(ptrb); + } + + @Override + protected int doHash(final FullData t) + { + return t.getSchema().hashCode() + ^ t.getInstance().getPointer().hashCode(); + } + }; +} diff --git a/src/main/java/com/github/fge/jsonschema/processors/validation/ObjectSchemaDigester.java b/src/main/java/com/github/fge/jsonschema/processors/validation/ObjectSchemaDigester.java index 98891de76..722ea8ac7 100644 --- a/src/main/java/com/github/fge/jsonschema/processors/validation/ObjectSchemaDigester.java +++ b/src/main/java/com/github/fge/jsonschema/processors/validation/ObjectSchemaDigester.java @@ -31,7 +31,7 @@ import java.util.Set; /** - * JSON Schema digester for an {@link ObjectSchemaDigester} + * JSON Schema digester for an {@link ObjectSchemaSelector} */ public final class ObjectSchemaDigester extends AbstractDigester diff --git a/src/main/java/com/github/fge/jsonschema/processors/validation/SchemaContextEquivalence.java b/src/main/java/com/github/fge/jsonschema/processors/validation/SchemaContextEquivalence.java index c978d41a6..8f07d7a68 100644 --- a/src/main/java/com/github/fge/jsonschema/processors/validation/SchemaContextEquivalence.java +++ b/src/main/java/com/github/fge/jsonschema/processors/validation/SchemaContextEquivalence.java @@ -19,8 +19,6 @@ package com.github.fge.jsonschema.processors.validation; -import com.github.fge.jsonschema.core.tree.SchemaTree; -import com.github.fge.jsonschema.core.util.equivalence.SchemaTreeEquivalence; import com.github.fge.jsonschema.processors.data.SchemaContext; import com.google.common.base.Equivalence; @@ -36,7 +34,6 @@ *
  • and the type of the instance is the same.
  • * * - * @see SchemaTreeEquivalence */ public final class SchemaContextEquivalence extends Equivalence @@ -44,9 +41,6 @@ public final class SchemaContextEquivalence private static final Equivalence INSTANCE = new SchemaContextEquivalence(); - private static final Equivalence TREE_EQUIVALENCE - = SchemaTreeEquivalence.getInstance(); - public static Equivalence getInstance() { return INSTANCE; @@ -55,14 +49,13 @@ public static Equivalence getInstance() @Override protected boolean doEquivalent(final SchemaContext a, final SchemaContext b) { - return TREE_EQUIVALENCE.equivalent(a.getSchema(), b.getSchema()) + return a.getSchema().equals(b.getSchema()) && a.getInstanceType() == b.getInstanceType(); } @Override protected int doHash(final SchemaContext t) { - return 31 * TREE_EQUIVALENCE.hash(t.getSchema()) - + t.getInstanceType().hashCode(); + return t.getSchema().hashCode() ^ t.getInstanceType().hashCode(); } } diff --git a/src/main/java/com/github/fge/jsonschema/processors/validation/ValidationChain.java b/src/main/java/com/github/fge/jsonschema/processors/validation/ValidationChain.java index 65062b4a7..bc4e4ad9e 100644 --- a/src/main/java/com/github/fge/jsonschema/processors/validation/ValidationChain.java +++ b/src/main/java/com/github/fge/jsonschema/processors/validation/ValidationChain.java @@ -30,7 +30,6 @@ import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.core.tree.SchemaTree; import com.github.fge.jsonschema.core.util.ValueHolder; -import com.github.fge.jsonschema.core.util.equivalence.SchemaTreeEquivalence; import com.github.fge.jsonschema.library.Library; import com.github.fge.jsonschema.processors.build.ValidatorBuilder; import com.github.fge.jsonschema.processors.data.SchemaContext; @@ -39,6 +38,8 @@ import com.github.fge.jsonschema.processors.format.FormatProcessor; import com.google.common.base.Equivalence; +import javax.annotation.ParametersAreNonnullByDefault; + /** * A validation chain * @@ -80,8 +81,7 @@ public ValidationChain(final RefResolver refResolver, = ProcessorChain.startWith(digester).chainWith(keywordBuilder); if (cfg.getUseFormat()) { - final FormatProcessor format - = new FormatProcessor(library, cfg); + final FormatProcessor format = new FormatProcessor(library, cfg); chain2 = chain2.chainWith(format); } @@ -120,26 +120,24 @@ public String toString() return resolver + " -> " + builder; } + @ParametersAreNonnullByDefault private static final class SchemaHolderEquivalence extends Equivalence> { private static final Equivalence> INSTANCE = new SchemaHolderEquivalence(); - private static final Equivalence EQUIVALENCE - = SchemaTreeEquivalence.getInstance(); - @Override protected boolean doEquivalent(final ValueHolder a, final ValueHolder b) { - return EQUIVALENCE.equivalent(a.getValue(), b.getValue()); + return a.getValue().equals(b.getValue()); } @Override protected int doHash(final ValueHolder t) { - return EQUIVALENCE.hash(t.getValue()); + return t.getValue().hashCode(); } } } diff --git a/src/main/java/com/github/fge/jsonschema/processors/validation/ValidationProcessor.java b/src/main/java/com/github/fge/jsonschema/processors/validation/ValidationProcessor.java index 2e6dde82f..4a5b94923 100644 --- a/src/main/java/com/github/fge/jsonschema/processors/validation/ValidationProcessor.java +++ b/src/main/java/com/github/fge/jsonschema/processors/validation/ValidationProcessor.java @@ -19,29 +19,14 @@ package com.github.fge.jsonschema.processors.validation; -import com.fasterxml.jackson.databind.JsonNode; -import com.github.fge.jackson.jsonpointer.JsonPointer; import com.github.fge.jsonschema.cfg.ValidationConfiguration; -import com.github.fge.jsonschema.core.exceptions.InvalidSchemaException; import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.processing.CachingProcessor; import com.github.fge.jsonschema.core.processing.Processor; -import com.github.fge.jsonschema.core.report.ProcessingMessage; import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.core.tree.JsonTree; -import com.github.fge.jsonschema.core.tree.SchemaTree; -import com.github.fge.jsonschema.keyword.validator.KeywordValidator; import com.github.fge.jsonschema.processors.data.FullData; import com.github.fge.jsonschema.processors.data.SchemaContext; import com.github.fge.jsonschema.processors.data.ValidatorList; import com.github.fge.msgsimple.bundle.MessageBundle; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.collect.Lists; - -import java.util.Collections; -import java.util.List; /** * Main validation processor @@ -52,19 +37,13 @@ public final class ValidationProcessor private final MessageBundle syntaxMessages; private final MessageBundle validationMessages; private final Processor processor; - private final LoadingCache arrayCache; - private final LoadingCache objectCache; public ValidationProcessor(final ValidationConfiguration cfg, final Processor processor) { syntaxMessages = cfg.getSyntaxMessages(); validationMessages = cfg.getValidationMessages(); - this.processor = new CachingProcessor( - processor, SchemaContextEquivalence.getInstance() - ); - arrayCache = CacheBuilder.newBuilder().build(arrayLoader()); - objectCache = CacheBuilder.newBuilder().build(objectLoader()); + this.processor = processor; } @Override @@ -72,138 +51,9 @@ public FullData process(final ProcessingReport report, final FullData input) throws ProcessingException { - /* - * Build a validation context, attach a report to it - */ - final SchemaContext context = new SchemaContext(input); - - /* - * Get the full context from the cache. Inject the messages into the - * main report. - */ - final ValidatorList fullContext = processor.process(report, context); - - if (fullContext == null) - throw new InvalidSchemaException(new ProcessingMessage() - .setMessage(syntaxMessages.getMessage("core.invalidSchema"))); - - /* - * Get the calculated context. Build the data. - */ - final SchemaContext newContext = fullContext.getContext(); - final FullData data = new FullData(newContext.getSchema(), - input.getInstance(), input.isDeepCheck()); - - /* - * Validate against all keywords. - */ - for (final KeywordValidator validator: fullContext) - validator.validate(this, report, validationMessages, data); - - /* - * At that point, if the report is a failure, we quit: there is no - * reason to go any further. Unless the user has asked to continue even - * in this case. - */ - if (!(report.isSuccess() || data.isDeepCheck())) - return input; - - /* - * Now check whether this is a container node with a size greater than - * 0. If not, no need to go see the children. - */ - final JsonNode node = data.getInstance().getNode(); - if (node.size() == 0) - return input; - - if (node.isArray()) - processArray(report, data); - else - processObject(report, data); - - return input; - } - - private void processArray(final ProcessingReport report, - final FullData input) - throws ProcessingException - { - final SchemaTree tree = input.getSchema(); - final JsonTree instance = input.getInstance(); - - final JsonNode schema = tree.getNode(); - final JsonNode node = instance.getNode(); - - final JsonNode digest = ArraySchemaDigester.getInstance().digest(schema); - final ArraySchemaSelector selector = arrayCache.getUnchecked(digest); - - final int size = node.size(); - - FullData data; - JsonTree newInstance; - - for (int index = 0; index < size; index++) { - newInstance = instance.append(JsonPointer.of(index)); - data = input.withInstance(newInstance); - for (final JsonPointer ptr: selector.selectSchemas(index)) { - data = data.withSchema(tree.append(ptr)); - process(report, data); - } - } - } - - private void processObject(final ProcessingReport report, - final FullData input) - throws ProcessingException - { - final SchemaTree tree = input.getSchema(); - final JsonTree instance = input.getInstance(); - - final JsonNode schema = tree.getNode(); - final JsonNode node = instance.getNode(); - - final JsonNode digest = ObjectSchemaDigester.getInstance() - .digest(schema); - final ObjectSchemaSelector selector = objectCache.getUnchecked(digest); - - final List fields = Lists.newArrayList(node.fieldNames()); - Collections.sort(fields); - - FullData data; - JsonTree newInstance; - - for (final String field: fields) { - newInstance = instance.append(JsonPointer.of(field)); - data = input.withInstance(newInstance); - for (final JsonPointer ptr: selector.selectSchemas(field)) { - data = data.withSchema(tree.append(ptr)); - process(report, data); - } - } - } - - private static CacheLoader arrayLoader() - { - return new CacheLoader() - { - @Override - public ArraySchemaSelector load(final JsonNode key) - { - return new ArraySchemaSelector(key); - } - }; - } - - private static CacheLoader objectLoader() - { - return new CacheLoader() - { - @Override - public ObjectSchemaSelector load(final JsonNode key) - { - return new ObjectSchemaSelector(key); - } - }; + final InstanceValidator validator = new InstanceValidator( + syntaxMessages, validationMessages, processor); + return validator.process(report, input); } @Override diff --git a/src/main/resources/com/github/fge/jsonschema/validator/validation.properties b/src/main/resources/com/github/fge/jsonschema/validator/validation.properties index 99ac7b4a7..b26f47504 100644 --- a/src/main/resources/com/github/fge/jsonschema/validator/validation.properties +++ b/src/main/resources/com/github/fge/jsonschema/validator/validation.properties @@ -74,3 +74,9 @@ err.format.jsonpointer.invalid = input string "%s" is not a valid JSON Pointer err.format.macAddr.invalid = input string "%s" is not a valid MAC address err.format.uriTemplate.invalid = input string "%s" is not a valid URI template err.format.UUID.invalid = input string "%s" is not a valid UUID + +# +# Other messages +# +err.common.validationLoop = validation loop: schema "%s" visited twice for \ + pointer "%s" of validated instance diff --git a/src/test/java/com/github/fge/jsonschema/format/AbstractFormatAttributeTest.java b/src/test/java/com/github/fge/jsonschema/format/AbstractFormatAttributeTest.java index 326e44b80..c02222d35 100644 --- a/src/test/java/com/github/fge/jsonschema/format/AbstractFormatAttributeTest.java +++ b/src/test/java/com/github/fge/jsonschema/format/AbstractFormatAttributeTest.java @@ -31,6 +31,7 @@ import com.github.fge.jsonschema.core.tree.JsonTree; import com.github.fge.jsonschema.core.tree.SchemaTree; import com.github.fge.jsonschema.core.tree.SimpleJsonTree; +import com.github.fge.jsonschema.core.tree.key.SchemaKey; import com.github.fge.jsonschema.core.util.Dictionary; import com.github.fge.jsonschema.messages.JsonSchemaValidationBundle; import com.github.fge.jsonschema.processors.data.FullData; @@ -55,8 +56,8 @@ public abstract class AbstractFormatAttributeTest { protected static final MessageBundle BUNDLE = MessageBundles.getBundle(JsonSchemaValidationBundle.class); - protected static final SchemaTree SCHEMA_TREE - = new CanonicalSchemaTree(JacksonUtils.nodeFactory().objectNode()); + protected static final SchemaTree SCHEMA_TREE = new CanonicalSchemaTree( + SchemaKey.anonymousKey(), JacksonUtils.nodeFactory().objectNode()); protected final FormatAttribute attribute; protected final String fmt; diff --git a/src/test/java/com/github/fge/jsonschema/keyword/special/ExtendsKeywordTest.java b/src/test/java/com/github/fge/jsonschema/keyword/special/ExtendsKeywordTest.java index da1e7b3a1..36bcee0c9 100644 --- a/src/test/java/com/github/fge/jsonschema/keyword/special/ExtendsKeywordTest.java +++ b/src/test/java/com/github/fge/jsonschema/keyword/special/ExtendsKeywordTest.java @@ -31,6 +31,7 @@ import com.github.fge.jsonschema.core.tree.JsonTree; import com.github.fge.jsonschema.core.tree.SchemaTree; import com.github.fge.jsonschema.core.tree.SimpleJsonTree; +import com.github.fge.jsonschema.core.tree.key.SchemaKey; import com.github.fge.jsonschema.keyword.validator.KeywordValidator; import com.github.fge.jsonschema.library.validator.DraftV3ValidatorDictionary; import com.github.fge.jsonschema.messages.JsonSchemaValidationBundle; @@ -81,7 +82,8 @@ public void initEnvironment() final ObjectNode schema = FACTORY.objectNode(); schema.put("extends", FACTORY.objectNode()); - final SchemaTree tree = new CanonicalSchemaTree(schema); + final SchemaTree tree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), schema); final JsonTree instance = new SimpleJsonTree(FACTORY.nullNode()); data = new FullData(tree, instance); diff --git a/src/test/java/com/github/fge/jsonschema/keyword/special/NotKeywordTest.java b/src/test/java/com/github/fge/jsonschema/keyword/special/NotKeywordTest.java index 54d3de0b3..3270529a3 100644 --- a/src/test/java/com/github/fge/jsonschema/keyword/special/NotKeywordTest.java +++ b/src/test/java/com/github/fge/jsonschema/keyword/special/NotKeywordTest.java @@ -32,6 +32,7 @@ import com.github.fge.jsonschema.core.tree.JsonTree; import com.github.fge.jsonschema.core.tree.SchemaTree; import com.github.fge.jsonschema.core.tree.SimpleJsonTree; +import com.github.fge.jsonschema.core.tree.key.SchemaKey; import com.github.fge.jsonschema.keyword.validator.KeywordValidator; import com.github.fge.jsonschema.library.validator.DraftV4ValidatorDictionary; import com.github.fge.jsonschema.messages.JsonSchemaValidationBundle; @@ -82,7 +83,8 @@ public void initEnvironment() final ObjectNode schema = FACTORY.objectNode(); schema.put("not", FACTORY.objectNode()); - final SchemaTree tree = new CanonicalSchemaTree(schema); + final SchemaTree tree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), schema); final JsonTree instance = new SimpleJsonTree(FACTORY.nullNode()); data = new FullData(tree, instance); report = mock(ProcessingReport.class); diff --git a/src/test/java/com/github/fge/jsonschema/keyword/special/PatternKeywordTest.java b/src/test/java/com/github/fge/jsonschema/keyword/special/PatternKeywordTest.java index 2f0bf17e3..6ff655ae0 100644 --- a/src/test/java/com/github/fge/jsonschema/keyword/special/PatternKeywordTest.java +++ b/src/test/java/com/github/fge/jsonschema/keyword/special/PatternKeywordTest.java @@ -31,6 +31,7 @@ import com.github.fge.jsonschema.core.tree.JsonTree; import com.github.fge.jsonschema.core.tree.SchemaTree; import com.github.fge.jsonschema.core.tree.SimpleJsonTree; +import com.github.fge.jsonschema.core.tree.key.SchemaKey; import com.github.fge.jsonschema.keyword.validator.KeywordValidator; import com.github.fge.jsonschema.library.validator.CommonValidatorDictionary; import com.github.fge.jsonschema.messages.JsonSchemaValidationBundle; @@ -108,7 +109,8 @@ public void instancesAreValidatedCorrectly(final JsonNode schema, throws IllegalAccessException, InvocationTargetException, InstantiationException, ProcessingException { - final SchemaTree tree = new CanonicalSchemaTree(schema); + final SchemaTree tree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), schema); final JsonTree instance = new SimpleJsonTree(node); final FullData data = new FullData(tree, instance); diff --git a/src/test/java/com/github/fge/jsonschema/keyword/validator/AbstractKeywordValidatorTest.java b/src/test/java/com/github/fge/jsonschema/keyword/validator/AbstractKeywordValidatorTest.java index c5e8a096a..aa69bcec9 100644 --- a/src/test/java/com/github/fge/jsonschema/keyword/validator/AbstractKeywordValidatorTest.java +++ b/src/test/java/com/github/fge/jsonschema/keyword/validator/AbstractKeywordValidatorTest.java @@ -31,6 +31,7 @@ import com.github.fge.jsonschema.core.tree.JsonTree; import com.github.fge.jsonschema.core.tree.SchemaTree; import com.github.fge.jsonschema.core.tree.SimpleJsonTree; +import com.github.fge.jsonschema.core.tree.key.SchemaKey; import com.github.fge.jsonschema.core.util.Dictionary; import com.github.fge.jsonschema.messages.JsonSchemaValidationBundle; import com.github.fge.jsonschema.processors.data.FullData; @@ -110,7 +111,8 @@ public final void instancesAreValidatedCorrectly(final JsonNode digest, InstantiationException, ProcessingException { // FIXME: dummy, but we have no choice - final SchemaTree tree = new CanonicalSchemaTree(digest); + final SchemaTree tree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), digest); final JsonTree instance = new SimpleJsonTree(node); final FullData data = new FullData(tree, instance); diff --git a/src/test/java/com/github/fge/jsonschema/keyword/validator/callback/CallbackValidatorTest.java b/src/test/java/com/github/fge/jsonschema/keyword/validator/callback/CallbackValidatorTest.java index b95c35d1c..07ba21ec8 100644 --- a/src/test/java/com/github/fge/jsonschema/keyword/validator/callback/CallbackValidatorTest.java +++ b/src/test/java/com/github/fge/jsonschema/keyword/validator/callback/CallbackValidatorTest.java @@ -33,6 +33,7 @@ import com.github.fge.jsonschema.core.tree.JsonTree; import com.github.fge.jsonschema.core.tree.SchemaTree; import com.github.fge.jsonschema.core.tree.SimpleJsonTree; +import com.github.fge.jsonschema.core.tree.key.SchemaKey; import com.github.fge.jsonschema.core.util.Dictionary; import com.github.fge.jsonschema.keyword.validator.KeywordValidator; import com.github.fge.jsonschema.messages.JsonSchemaValidationBundle; @@ -88,7 +89,8 @@ protected final void initEnvironment() if (constructor == null) return; - final SchemaTree tree = new CanonicalSchemaTree(generateSchema()); + final SchemaTree tree = new CanonicalSchemaTree( + SchemaKey.anonymousKey(), generateSchema()); final JsonTree instance = new SimpleJsonTree(generateInstance()); data = new FullData(tree, instance); report = mock(ProcessingReport.class); diff --git a/src/test/java/com/github/fge/jsonschema/processors/digest/SchemaDigesterTest.java b/src/test/java/com/github/fge/jsonschema/processors/digest/SchemaDigesterTest.java index beed7b881..6a1866caa 100644 --- a/src/test/java/com/github/fge/jsonschema/processors/digest/SchemaDigesterTest.java +++ b/src/test/java/com/github/fge/jsonschema/processors/digest/SchemaDigesterTest.java @@ -29,6 +29,7 @@ import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.core.tree.CanonicalSchemaTree; import com.github.fge.jsonschema.core.tree.SchemaTree; +import com.github.fge.jsonschema.core.tree.key.SchemaKey; import com.github.fge.jsonschema.core.util.Dictionary; import com.github.fge.jsonschema.core.util.DictionaryBuilder; import com.github.fge.jsonschema.keyword.digest.Digester; @@ -100,7 +101,8 @@ public void onlyRelevantDigestsAreBuilt(final JsonNode node) throws ProcessingException { final NodeType type = NodeType.getNodeType(node); - final SchemaTree tree = new CanonicalSchemaTree(schema); + final SchemaTree tree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), schema); final SchemaContext context = new SchemaContext(tree, type); final ProcessingReport report = mock(ProcessingReport.class); @@ -129,7 +131,8 @@ public void nonPresentKeywordDoesNotTriggerBuild() { final ObjectNode node = FACTORY.objectNode(); node.put(K1, K1); - final SchemaTree schemaTree = new CanonicalSchemaTree(node); + final SchemaTree schemaTree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), node); final SchemaContext context = new SchemaContext(schemaTree, NodeType.NULL); final ProcessingReport report = mock(ProcessingReport.class); diff --git a/src/test/java/com/github/fge/jsonschema/processors/format/FormatProcessorTest.java b/src/test/java/com/github/fge/jsonschema/processors/format/FormatProcessorTest.java index 5dd4b621d..3d40527e4 100644 --- a/src/test/java/com/github/fge/jsonschema/processors/format/FormatProcessorTest.java +++ b/src/test/java/com/github/fge/jsonschema/processors/format/FormatProcessorTest.java @@ -33,6 +33,7 @@ import com.github.fge.jsonschema.core.tree.JsonTree; import com.github.fge.jsonschema.core.tree.SchemaTree; import com.github.fge.jsonschema.core.tree.SimpleJsonTree; +import com.github.fge.jsonschema.core.tree.key.SchemaKey; import com.github.fge.jsonschema.core.util.Dictionary; import com.github.fge.jsonschema.format.FormatAttribute; import com.github.fge.jsonschema.keyword.validator.KeywordValidator; @@ -87,7 +88,8 @@ public void noFormatInSchemaIsANoOp() throws ProcessingException { final ObjectNode schema = FACTORY.objectNode(); - final SchemaTree tree = new CanonicalSchemaTree(schema); + final SchemaTree tree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), schema); final SchemaContext context = new SchemaContext(tree, NodeType.NULL); final ValidatorList in = new ValidatorList(context, Collections.emptyList()); @@ -105,7 +107,8 @@ public void unknownFormatAttributesAreReportedAsWarnings() { final ObjectNode schema = FACTORY.objectNode(); schema.put("format", "foo"); - final SchemaTree tree = new CanonicalSchemaTree(schema); + final SchemaTree tree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), schema); final SchemaContext context = new SchemaContext(tree, NodeType.NULL); final ValidatorList in = new ValidatorList(context, Collections.emptyList()); @@ -133,7 +136,8 @@ public void attributeIsBeingAskedWhatIsSupports() { final ObjectNode schema = FACTORY.objectNode(); schema.put("format", FMT); - final SchemaTree tree = new CanonicalSchemaTree(schema); + final SchemaTree tree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), schema); final SchemaContext context = new SchemaContext(tree, NodeType.NULL); final ValidatorList in = new ValidatorList(context, Collections.emptyList()); @@ -157,7 +161,8 @@ public void supportedNodeTypesTriggerAttributeBuild(final JsonNode node) { final ObjectNode schema = FACTORY.objectNode(); schema.put("format", FMT); - final SchemaTree tree = new CanonicalSchemaTree(schema); + final SchemaTree tree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), schema); final JsonTree instance = new SimpleJsonTree(node); final FullData data = new FullData(tree, instance); final SchemaContext context = new SchemaContext(data); @@ -192,7 +197,8 @@ public void unsupportedTypeDoesNotTriggerValidatorBuild(final JsonNode node) { final ObjectNode schema = FACTORY.objectNode(); schema.put("format", FMT); - final SchemaTree tree = new CanonicalSchemaTree(schema); + final SchemaTree tree + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), schema); final SchemaContext context = new SchemaContext(tree, NodeType.getNodeType(node)); final ValidatorList in = new ValidatorList(context, diff --git a/src/test/java/com/github/fge/jsonschema/processors/validation/ValidationProcessorTest.java b/src/test/java/com/github/fge/jsonschema/processors/validation/ValidationProcessorTest.java index 65af8a23c..41d828eda 100644 --- a/src/test/java/com/github/fge/jsonschema/processors/validation/ValidationProcessorTest.java +++ b/src/test/java/com/github/fge/jsonschema/processors/validation/ValidationProcessorTest.java @@ -24,30 +24,44 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.fge.jackson.JacksonUtils; +import com.github.fge.jackson.JsonLoader; import com.github.fge.jackson.NodeType; +import com.github.fge.jackson.jsonpointer.JsonPointer; +import com.github.fge.jackson.jsonpointer.JsonPointerException; import com.github.fge.jsonschema.cfg.ValidationConfiguration; import com.github.fge.jsonschema.core.exceptions.ProcessingException; import com.github.fge.jsonschema.core.keyword.syntax.checkers.SyntaxChecker; import com.github.fge.jsonschema.core.processing.Processor; +import com.github.fge.jsonschema.core.report.ProcessingMessage; import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.core.tree.CanonicalSchemaTree; import com.github.fge.jsonschema.core.tree.JsonTree; import com.github.fge.jsonschema.core.tree.SchemaTree; import com.github.fge.jsonschema.core.tree.SimpleJsonTree; +import com.github.fge.jsonschema.core.tree.key.SchemaKey; import com.github.fge.jsonschema.keyword.validator.AbstractKeywordValidator; import com.github.fge.jsonschema.library.DraftV4Library; import com.github.fge.jsonschema.library.Keyword; import com.github.fge.jsonschema.library.Library; import com.github.fge.jsonschema.main.JsonSchemaFactory; +import com.github.fge.jsonschema.main.JsonValidator; +import com.github.fge.jsonschema.messages.JsonSchemaValidationBundle; import com.github.fge.jsonschema.processors.data.FullData; import com.github.fge.msgsimple.bundle.MessageBundle; +import com.github.fge.msgsimple.load.MessageBundles; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; -import static org.mockito.Mockito.*; -import static org.testng.Assert.*; +import static com.github.fge.jsonschema.matchers.ProcessingMessageAssert.assertMessage; +import static org.mockito.Mockito.mock; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; public final class ValidationProcessorTest { @@ -91,7 +105,8 @@ public void init() public void childrenAreNotExploredByDefaultIfContainerFails() throws ProcessingException { - final SchemaTree schema = new CanonicalSchemaTree(RAWSCHEMA); + final SchemaTree schema + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), RAWSCHEMA); final JsonTree instance = new SimpleJsonTree(RAWINSTANCE); final FullData data = new FullData(schema, instance); final ProcessingReport report = mock(ProcessingReport.class); @@ -103,7 +118,8 @@ public void childrenAreNotExploredByDefaultIfContainerFails() public void childrenAreExploredOnDemandEvenIfContainerFails() throws ProcessingException { - final SchemaTree schema = new CanonicalSchemaTree(RAWSCHEMA); + final SchemaTree schema + = new CanonicalSchemaTree(SchemaKey.anonymousKey(), RAWSCHEMA); final JsonTree instance = new SimpleJsonTree(RAWINSTANCE); final FullData data = new FullData(schema, instance, true); final ProcessingReport report = mock(ProcessingReport.class); @@ -111,6 +127,36 @@ public void childrenAreExploredOnDemandEvenIfContainerFails() assertEquals(COUNT.get(), 1); } + @Test(timeOut = 1000) + public void circularReferencingDuringValidationIsDetected() + throws IOException, ProcessingException, JsonPointerException + { + final JsonNode schemaNode + = JsonLoader.fromResource("/other/issue102.json"); + final JsonSchemaFactory factory = JsonSchemaFactory.byDefault(); + final JsonValidator validator = factory.getValidator(); + final MessageBundle bundle + = MessageBundles.getBundle(JsonSchemaValidationBundle.class); + + try { + validator.validate(schemaNode, + JacksonUtils.nodeFactory().nullNode()); + fail("No exception thrown!"); + } catch (ProcessingException e) { + final URI uri = URI.create("#/oneOf/0"); + final ProcessingMessage message = e.getProcessingMessage(); + final String expectedMessage + = bundle.printf("err.common.validationLoop", uri, ""); + assertMessage(message) + .hasMessage(expectedMessage) + .hasField("alreadyVisited", uri) + .hasField("instancePointer", JsonPointer.empty().toString()) + .hasField("validationPath", + Arrays.asList("#", "#/oneOf/0", "#/oneOf/1")); + } + assertTrue(true); + } + public static final class K1Validator extends AbstractKeywordValidator { diff --git a/src/test/resources/other/issue102.json b/src/test/resources/other/issue102.json new file mode 100644 index 000000000..1144e4a1e --- /dev/null +++ b/src/test/resources/other/issue102.json @@ -0,0 +1 @@ +{ "oneOf": [ {}, { "$ref": "#" } ] } \ No newline at end of file