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..a59a0c25d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,75 +1,24 @@ -### 2.1.10 +### 2.2.4 -* Plug in hyperschema syntax support... -* Gradle 1.11. -* Add a main method. -* -core 1.1.11. +* 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.9 +### 2.2.3 -* 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. +* Re-release... 2.2.2 was compiled with JDK 8 :/ -### 2.1.8 +### 2.2.2 -* 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) +* Depend on -core 1.2.1 to work around Rhino bug with other packages sealing the + context. -### 2.1.7 +### 2.2.1 -* 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. +* Main now uses current working directory as default URI namespace. -### 2.1.6 +### 2.2.0 -* 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. - -### 2.1.5 - -* 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..5d67bc776 100644 --- a/build.gradle +++ b/build.gradle @@ -72,6 +72,7 @@ apply(from: "project.gradle"); */ repositories { mavenCentral(); + mavenLocal(); } /* @@ -128,11 +129,24 @@ task fullJar(type: OneJar) { 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; +} + artifacts { archives jar; archives sourcesJar; archives javadocJar; archives fullJar; + archives libJar; } task wrapper(type: Wrapper) { diff --git a/project.gradle b/project.gradle index c74d710aa..180ed8352 100644 --- a/project.gradle +++ b/project.gradle @@ -21,7 +21,7 @@ * Project-specific settings. Unfortunately we cannot put the name in there! */ group = "com.github.fge"; -version = "2.2.0"; +version = "2.2.5-SNAPSHOT"; sourceCompatibility = "1.6"; targetCompatibility = "1.6"; // defaults to sourceCompatibility @@ -34,7 +34,7 @@ project.ext { */ dependencies { compile(group: "com.github.fge", name: "json-schema-core", - version: "1.2.0"); + version: "1.2.4-SNAPSHOT"); 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", 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/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..c824c14e8 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 @@ -20,6 +20,8 @@ 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.cfg.ValidationConfiguration; import com.github.fge.jsonschema.core.exceptions.InvalidSchemaException; @@ -83,9 +85,27 @@ public FullData process(final ProcessingReport report, */ final ValidatorList fullContext = processor.process(report, context); - if (fullContext == null) - throw new InvalidSchemaException(new ProcessingMessage() - .setMessage(syntaxMessages.getMessage("core.invalidSchema"))); + if (fullContext == null) { + /* + * 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)); + final ProcessingMessage message = new ProcessingMessage() + .setMessage(sb.toString()); + throw new InvalidSchemaException(message); + } /* * Get the calculated context. Build the data. 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..380eb5c22 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,8 @@ 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 detected 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..5fa85df26 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,6 +24,7 @@ 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.jsonschema.cfg.ValidationConfiguration; import com.github.fge.jsonschema.core.exceptions.ProcessingException; @@ -39,15 +40,21 @@ 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.util.concurrent.atomic.AtomicInteger; -import static org.mockito.Mockito.*; -import static org.testng.Assert.*; +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 { @@ -111,6 +118,29 @@ public void childrenAreExploredOnDemandEvenIfContainerFails() assertEquals(COUNT.get(), 1); } + @Test(timeOut = 1000) + public void circularReferencingDuringValidationIsDetected() + throws IOException, ProcessingException + { + 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); + final String expectedMsg + = bundle.getMessage("err.common.validationLoop"); + + try { + validator.validate(schemaNode, + JacksonUtils.nodeFactory().nullNode()); + fail("No exception thrown!"); + } catch (ProcessingException e) { + assertEquals(e.getMessage(), expectedMsg); + } + 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..1af9832c3 --- /dev/null +++ b/src/test/resources/other/issue102.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "S": { + "allOf": [ + { + "type": "string", + "enum": ["a"] + }, + { + "oneOf": [ + { + "$ref": "#/definitions/S" + }, + { + "type": "string", + "enum": [""] + } + ] + }, + { + "type": "string", + "enum": ["b"] + } + ], + "additionalItems": false + } + }, + "$ref": "#/definitions/S" +} \ No newline at end of file