diff --git a/README.md b/README.md index f1fb60f..b5705c2 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ import via maven: es.elixir.bsc.json.schema jaronuinga - 0.5.5 + 0.5.6 ... @@ -24,7 +24,7 @@ import via maven: The simplest usage: ```java JsonSchema schema = JsonSchemaReader.getReader().read(url); // parse JsonSchema from the URL location -List errors = new ArrayList<>(); // array to collect errors +List errors = new ArrayList(); // array to collect errors schema.validate(json, errors); // validate JsonObject ``` Note that instead of URL users could provide their own schema locators. @@ -34,7 +34,7 @@ to resolve "$ref" Json Pointers. To provide flexibility it is possible to get callbacks during the validation process. ```java schema.validate(json, errors, ( - PrimitiveSchema subschema, String pointer, JsonValue value, JsonValue parent, List err) -> { + JsonSchema subschema, String pointer, JsonValue value, JsonValue parent, List err) -> { }); ``` Here above we have: @@ -44,19 +44,19 @@ Here above we have: - parent - a parent of currently validating Json value - err - collected validation errors so far -Note, that providing ExtendedJsonSchemaLocator (which collects all subschemas as originated jsons), we can -associate validated JsonValue with corresponding Json Object which describes the schema: ```java -JsonSchema schema = JsonSchemaReader.getReader().read(locator); +JsonSchemaReader reader = JsonSchemaReader.getReader(); +JsonSchemaLocator locator = reader.getJsonSchemaLocator(uri); +JsonSchema schema = reader.read(locator); schema.validate(json, errors, ( - PrimitiveSchema subschema, String pointer, JsonValue value, JsonValue parent, List err) -> { + JsonSchema subschema, String pointer, JsonValue value, JsonValue parent, List err) -> { JsonObject subschemaJsonObject = locator.getSchema(subschema.getId(), subschema.getJsonPointer()); }); ``` We can also stop further parsing on error via the callback: ```java schema.validate(json, errors, ( - PrimitiveSchema subschema, String pointer, JsonValue value, JsonValue parent, List err) -> { + JsonSchema subschema, String pointer, JsonValue value, JsonValue parent, List err) -> { throw new ValidationException(new ValidationError(subschema.getId(), subschema.getJsonPointer(), "")); }); ``` diff --git a/pom.xml b/pom.xml index 9cec404..2a138be 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ es.elixir.bsc.json.schema jaronuinga - 0.5.5 + 0.5.6 jar diff --git a/src/main/java/es/elixir/bsc/json/schema/JsonSchemaReader.java b/src/main/java/es/elixir/bsc/json/schema/JsonSchemaReader.java index e0a1ace..8cd6bb6 100644 --- a/src/main/java/es/elixir/bsc/json/schema/JsonSchemaReader.java +++ b/src/main/java/es/elixir/bsc/json/schema/JsonSchemaReader.java @@ -1,6 +1,6 @@ /** * ***************************************************************************** - * Copyright (C) 2023 ELIXIR ES, Spanish National Bioinformatics Institute (INB) + * Copyright (C) 2024 ELIXIR ES, Spanish National Bioinformatics Institute (INB) * and Barcelona Supercomputing Center (BSC) * * Modifications to the initial code base are copyright of their respective @@ -26,6 +26,7 @@ package es.elixir.bsc.json.schema; import es.elixir.bsc.json.schema.model.JsonSchema; +import java.net.URI; import java.net.URL; import java.util.Collections; import java.util.Iterator; @@ -39,21 +40,70 @@ public interface JsonSchemaReader { /** + * Read JSON Schema located by the provided URL. + * The reader uses a new instance of JsonSchemaLocator. * - * @param url the URL to read Json Schema from. + * @param url the URL to read a JSON Schema from. * @return parsed JsonSchema object + * * @throws JsonSchemaException */ JsonSchema read(URL url) throws JsonSchemaException; + + /** + * Read JSON Schema located by the provided JsonSchemaLocator. + * This allows reuse the same JsonSchemaLocator instance between calls. + * + * @param locator JsonSchemaLocator to locate parsed JSON Schema. + * @return parsed JsonSchema object + * + * @throws JsonSchemaException + */ JsonSchema read(JsonSchemaLocator locator) throws JsonSchemaException; + /** + * Creates new instance of default implementation of the JsonSchemaLocator. + * + * @param uri the JSON Schema location URI + * + * @return JsonSchemaLocator + */ + JsonSchemaLocator getJsonSchemaLocator(URI uri); + + /** + * Set configuration property for this JsonSchemaReader. + * + * @param name configuration property name + * @param property configuration property value + */ void setJsonSchemaParserProperty(String name, Object property); - - public static JsonSchemaReader getReader() { + + /** + * Create JsonSchemaReader with no configuration parameters. + * + * @return new instance of the JsonSchemaReader + */ + static JsonSchemaReader getReader() { return getReader(Collections.EMPTY_MAP); } - public static JsonSchemaReader getReader(Map config) { + /** + * Create JsonSchemaReader with provided configuration properties. + * + *
+     * example:
+     * {@code
+     * JsonSchemaParserConfig config = 
+     *     new JsonSchemaParserConfig()
+     *         .setJsonSchemaVersion(JsonSchemaVersion.SCHEMA_DRAFT_2020_12);
+     * JsonSchemaReader reader = JsonSchemaReader.getReader(config);
+     * }
+ * + * @param config the map of configuration properties + * + * @return new instance of the JsonSchemaReader + */ + static JsonSchemaReader getReader(Map config) { ServiceLoader loader = ServiceLoader.load(JsonSchemaReader.class); Iterator iterator = loader.iterator(); diff --git a/src/main/java/es/elixir/bsc/json/schema/JsonSchemaValidationCallback.java b/src/main/java/es/elixir/bsc/json/schema/JsonSchemaValidationCallback.java index 9435d7e..437c522 100644 --- a/src/main/java/es/elixir/bsc/json/schema/JsonSchemaValidationCallback.java +++ b/src/main/java/es/elixir/bsc/json/schema/JsonSchemaValidationCallback.java @@ -1,6 +1,6 @@ /** * ***************************************************************************** - * Copyright (C) 2022 ELIXIR ES, Spanish National Bioinformatics Institute (INB) + * Copyright (C) 2024 ELIXIR ES, Spanish National Bioinformatics Institute (INB) * and Barcelona Supercomputing Center (BSC) * * Modifications to the initial code base are copyright of their respective @@ -25,7 +25,7 @@ package es.elixir.bsc.json.schema; -import es.elixir.bsc.json.schema.model.PrimitiveSchema; +import es.elixir.bsc.json.schema.model.JsonSchema; import java.util.List; /** @@ -39,8 +39,11 @@ public interface JsonSchemaValidationCallback { /** - * Callback method called by the validator after the value is validated.Implementations may include custom validation errors into the errors list - or stop validation process by throwing the ValidationException. + * Callback method called by the validator after the value is validated. + * Implementations may include custom validation errors into the errors list + * or stop validation process by throwing the ValidationException. + * Note that validation errors not necessary mean the schema is invalid - + * this may be a part of evaluation of 'oneOf', for example. * * @param schema json schema model to validate json value. * @param pointer json pointer to the validated value @@ -50,5 +53,6 @@ public interface JsonSchemaValidationCallback { * * @throws ValidationException the exception to be thrown by the validator. */ - void validated(PrimitiveSchema schema, String pointer, T value, T parent, List errors) throws ValidationException; + void validated(JsonSchema schema, String pointer, T value, T parent, + List errors) throws ValidationException; } \ No newline at end of file diff --git a/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaLocator.java b/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaLocator.java index 48eb0ed..356f718 100644 --- a/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaLocator.java +++ b/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaLocator.java @@ -1,6 +1,6 @@ /** * ***************************************************************************** - * Copyright (C) 2022 ELIXIR ES, Spanish National Bioinformatics Institute (INB) + * Copyright (C) 2024 ELIXIR ES, Spanish National Bioinformatics Institute (INB) * and Barcelona Supercomputing Center (BSC) * * Modifications to the initial code base are copyright of their respective @@ -59,7 +59,7 @@ public class DefaultJsonSchemaLocator extends JsonSchemaLocator { protected final Map schemas; public DefaultJsonSchemaLocator(URI uri) { - this(uri, new HashMap<>()); + this(uri, new HashMap()); } protected DefaultJsonSchemaLocator(URI uri, Map schemas) { diff --git a/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaParser.java b/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaParser.java index 950cf52..763ed3e 100644 --- a/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaParser.java +++ b/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaParser.java @@ -86,6 +86,9 @@ public AbstractJsonSchema parse(JsonSchemaLocator locator, AbstractJsonSchemaEle String jsonPointer, JsonValue value, JsonType type) throws JsonSchemaException { + // much easily catch 'invalid' root json pointer here than check for the '/' root in callers. + jsonPointer = jsonPointer.startsWith("//") ? jsonPointer.substring(1) : jsonPointer; + AbstractJsonSchema schema = cache.get(locator, jsonPointer); if (schema != null) { return schema; diff --git a/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaReader.java b/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaReader.java index c05a754..10aea74 100644 --- a/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaReader.java +++ b/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaReader.java @@ -60,6 +60,11 @@ public void setJsonSchemaParserProperty(String name, Object property) { properties.put(name, property); } + @Override + public JsonSchemaLocator getJsonSchemaLocator(URI uri) { + return new DefaultJsonSchemaLocator(uri); + } + @Override public JsonSchema read(URL url) throws JsonSchemaException { try { diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchemaElement.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchemaElement.java index 7772fdd..0e3e233 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchemaElement.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchemaElement.java @@ -61,9 +61,8 @@ public AbstractJsonSchemaElement(AbstractJsonSchemaElement parent, JsonSchemaLocator locator, String jsonPointer) { this.parent = parent; - this.locator = locator; - this.jsonPointer = jsonPointer.startsWith("//") ? jsonPointer.substring(1) : jsonPointer; + this.jsonPointer = jsonPointer; } @Override diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonNumberSchemaImpl.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonNumberSchemaImpl.java index d3d06b2..303a193 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonNumberSchemaImpl.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonNumberSchemaImpl.java @@ -92,7 +92,7 @@ public boolean validate(String jsonPointer, JsonValue value, JsonValue parent, return nerrors == errors.size(); } - + private void validate(String jsonPointer, BigDecimal dec, List errors) { if (minimum != null) { diff --git a/src/test/java/es/elixir/bsc/json/schema/JsonSchemaValidationCallbackTest.java b/src/test/java/es/elixir/bsc/json/schema/JsonSchemaValidationCallbackTest.java index f13df79..60144f9 100644 --- a/src/test/java/es/elixir/bsc/json/schema/JsonSchemaValidationCallbackTest.java +++ b/src/test/java/es/elixir/bsc/json/schema/JsonSchemaValidationCallbackTest.java @@ -26,7 +26,6 @@ package es.elixir.bsc.json.schema; import es.elixir.bsc.json.schema.model.JsonSchema; -import es.elixir.bsc.json.schema.model.PrimitiveSchema; import javax.json.Json; import javax.json.JsonStructure; import javax.json.JsonValue; @@ -62,7 +61,8 @@ public void test_01() { final AtomicInteger counter = new AtomicInteger(); List errors = new ArrayList<>(); - schema.validate(json, errors, (PrimitiveSchema model, String pointer, JsonValue value, JsonValue parent, List err) -> { + schema.validate(json, errors, (JsonSchema model, String pointer, + JsonValue value, JsonValue parent, List err) -> { counter.incrementAndGet(); }); diff --git a/src/test/resources/json-schema-org/remotes/draft3/subSchemas.json b/src/test/resources/json-schema-org/remotes/draft3/subSchemas.json new file mode 100644 index 0000000..6e9b3de --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft3/subSchemas.json @@ -0,0 +1,10 @@ +{ + "definitions": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/definitions/integer" + } + } +} diff --git a/src/test/resources/json-schema-org/remotes/draft4/locationIndependentIdentifier.json b/src/test/resources/json-schema-org/remotes/draft4/locationIndependentIdentifier.json new file mode 100644 index 0000000..eeff1eb --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft4/locationIndependentIdentifier.json @@ -0,0 +1,11 @@ +{ + "definitions": { + "refToInteger": { + "$ref": "#foo" + }, + "A": { + "id": "#foo", + "type": "integer" + } + } +} diff --git a/src/test/resources/json-schema-org/remotes/draft4/name.json b/src/test/resources/json-schema-org/remotes/draft4/name.json new file mode 100644 index 0000000..fceacb8 --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft4/name.json @@ -0,0 +1,15 @@ +{ + "definitions": { + "orNull": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#" + } + ] + } + }, + "type": "string" +} diff --git a/src/test/resources/json-schema-org/remotes/draft4/subSchemas.json b/src/test/resources/json-schema-org/remotes/draft4/subSchemas.json new file mode 100644 index 0000000..6e9b3de --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft4/subSchemas.json @@ -0,0 +1,10 @@ +{ + "definitions": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/definitions/integer" + } + } +} diff --git a/src/test/resources/json-schema-org/remotes/draft6/locationIndependentIdentifier.json b/src/test/resources/json-schema-org/remotes/draft6/locationIndependentIdentifier.json new file mode 100644 index 0000000..e72815c --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft6/locationIndependentIdentifier.json @@ -0,0 +1,11 @@ +{ + "definitions": { + "refToInteger": { + "$ref": "#foo" + }, + "A": { + "$id": "#foo", + "type": "integer" + } + } +} diff --git a/src/test/resources/json-schema-org/remotes/draft6/name.json b/src/test/resources/json-schema-org/remotes/draft6/name.json new file mode 100644 index 0000000..fceacb8 --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft6/name.json @@ -0,0 +1,15 @@ +{ + "definitions": { + "orNull": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#" + } + ] + } + }, + "type": "string" +} diff --git a/src/test/resources/json-schema-org/remotes/draft6/ref-and-definitions.json b/src/test/resources/json-schema-org/remotes/draft6/ref-and-definitions.json new file mode 100644 index 0000000..b80deeb --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft6/ref-and-definitions.json @@ -0,0 +1,11 @@ +{ + "$id": "http://localhost:1234/draft6/ref-and-definitions.json", + "definitions": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "allOf": [ { "$ref": "#/definitions/inner" } ] +} diff --git a/src/test/resources/json-schema-org/remotes/draft6/subSchemas.json b/src/test/resources/json-schema-org/remotes/draft6/subSchemas.json new file mode 100644 index 0000000..6e9b3de --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft6/subSchemas.json @@ -0,0 +1,10 @@ +{ + "definitions": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/definitions/integer" + } + } +} diff --git a/src/test/resources/json-schema-org/remotes/draft7/locationIndependentIdentifier.json b/src/test/resources/json-schema-org/remotes/draft7/locationIndependentIdentifier.json new file mode 100644 index 0000000..e72815c --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft7/locationIndependentIdentifier.json @@ -0,0 +1,11 @@ +{ + "definitions": { + "refToInteger": { + "$ref": "#foo" + }, + "A": { + "$id": "#foo", + "type": "integer" + } + } +} diff --git a/src/test/resources/json-schema-org/remotes/draft7/name.json b/src/test/resources/json-schema-org/remotes/draft7/name.json new file mode 100644 index 0000000..fceacb8 --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft7/name.json @@ -0,0 +1,15 @@ +{ + "definitions": { + "orNull": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#" + } + ] + } + }, + "type": "string" +} diff --git a/src/test/resources/json-schema-org/remotes/draft7/ref-and-definitions.json b/src/test/resources/json-schema-org/remotes/draft7/ref-and-definitions.json new file mode 100644 index 0000000..d592938 --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft7/ref-and-definitions.json @@ -0,0 +1,11 @@ +{ + "$id": "http://localhost:1234/draft7/ref-and-definitions.json", + "definitions": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "allOf": [ { "$ref": "#/definitions/inner" } ] +} diff --git a/src/test/resources/json-schema-org/remotes/draft7/subSchemas.json b/src/test/resources/json-schema-org/remotes/draft7/subSchemas.json new file mode 100644 index 0000000..6e9b3de --- /dev/null +++ b/src/test/resources/json-schema-org/remotes/draft7/subSchemas.json @@ -0,0 +1,10 @@ +{ + "definitions": { + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/definitions/integer" + } + } +} diff --git a/src/test/resources/json-schema-org/tests/draft4/refRemote.json b/src/test/resources/json-schema-org/tests/draft4/refRemote.json index 64a618b..65e4519 100644 --- a/src/test/resources/json-schema-org/tests/draft4/refRemote.json +++ b/src/test/resources/json-schema-org/tests/draft4/refRemote.json @@ -17,7 +17,7 @@ }, { "description": "fragment within remote ref", - "schema": {"$ref": "http://localhost:1234/subSchemas.json#/definitions/integer"}, + "schema": {"$ref": "http://localhost:1234/draft4/subSchemas.json#/definitions/integer"}, "tests": [ { "description": "remote fragment valid", @@ -34,7 +34,7 @@ { "description": "ref within remote ref", "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/definitions/refToInteger" + "$ref": "http://localhost:1234/draft4/subSchemas.json#/definitions/refToInteger" }, "tests": [ { @@ -139,7 +139,7 @@ "id": "http://localhost:1234/object", "type": "object", "properties": { - "name": {"$ref": "name.json#/definitions/orNull"} + "name": {"$ref": "draft4/name.json#/definitions/orNull"} } }, "tests": [ @@ -171,7 +171,7 @@ { "description": "Location-independent identifier in remote ref", "schema": { - "$ref": "http://localhost:1234/locationIndependentIdentifierDraft4.json#/definitions/refToInteger" + "$ref": "http://localhost:1234/draft4/locationIndependentIdentifier.json#/definitions/refToInteger" }, "tests": [ { diff --git a/src/test/resources/json-schema-org/tests/draft6/refRemote.json b/src/test/resources/json-schema-org/tests/draft6/refRemote.json index 28459c4..49ead6d 100644 --- a/src/test/resources/json-schema-org/tests/draft6/refRemote.json +++ b/src/test/resources/json-schema-org/tests/draft6/refRemote.json @@ -17,7 +17,7 @@ }, { "description": "fragment within remote ref", - "schema": {"$ref": "http://localhost:1234/subSchemas.json#/definitions/integer"}, + "schema": {"$ref": "http://localhost:1234/draft6/subSchemas.json#/definitions/integer"}, "tests": [ { "description": "remote fragment valid", @@ -34,7 +34,7 @@ { "description": "ref within remote ref", "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/definitions/refToInteger" + "$ref": "http://localhost:1234/draft6/subSchemas.json#/definitions/refToInteger" }, "tests": [ { @@ -139,7 +139,7 @@ "$id": "http://localhost:1234/object", "type": "object", "properties": { - "name": {"$ref": "name.json#/definitions/orNull"} + "name": {"$ref": "draft6/name.json#/definitions/orNull"} } }, "tests": [ @@ -173,7 +173,7 @@ "schema": { "$id": "http://localhost:1234/schema-remote-ref-ref-defs1.json", "allOf": [ - { "$ref": "ref-and-definitions.json" } + { "$ref": "draft6/ref-and-definitions.json" } ] }, "tests": [ @@ -196,7 +196,7 @@ { "description": "Location-independent identifier in remote ref", "schema": { - "$ref": "http://localhost:1234/locationIndependentIdentifierPre2019.json#/definitions/refToInteger" + "$ref": "http://localhost:1234/draft6/locationIndependentIdentifier.json#/definitions/refToInteger" }, "tests": [ { diff --git a/src/test/resources/json-schema-org/tests/draft7/refRemote.json b/src/test/resources/json-schema-org/tests/draft7/refRemote.json index 22185d6..450787a 100644 --- a/src/test/resources/json-schema-org/tests/draft7/refRemote.json +++ b/src/test/resources/json-schema-org/tests/draft7/refRemote.json @@ -17,7 +17,7 @@ }, { "description": "fragment within remote ref", - "schema": {"$ref": "http://localhost:1234/subSchemas.json#/definitions/integer"}, + "schema": {"$ref": "http://localhost:1234/draft7/subSchemas.json#/definitions/integer"}, "tests": [ { "description": "remote fragment valid", @@ -34,7 +34,7 @@ { "description": "ref within remote ref", "schema": { - "$ref": "http://localhost:1234/subSchemas.json#/definitions/refToInteger" + "$ref": "http://localhost:1234/draft7/subSchemas.json#/definitions/refToInteger" }, "tests": [ { @@ -139,7 +139,7 @@ "$id": "http://localhost:1234/object", "type": "object", "properties": { - "name": {"$ref": "name.json#/definitions/orNull"} + "name": {"$ref": "draft7/name.json#/definitions/orNull"} } }, "tests": [ @@ -173,7 +173,7 @@ "schema": { "$id": "http://localhost:1234/schema-remote-ref-ref-defs1.json", "allOf": [ - { "$ref": "ref-and-definitions.json" } + { "$ref": "draft7/ref-and-definitions.json" } ] }, "tests": [ @@ -196,7 +196,7 @@ { "description": "Location-independent identifier in remote ref", "schema": { - "$ref": "http://localhost:1234/locationIndependentIdentifierPre2019.json#/definitions/refToInteger" + "$ref": "http://localhost:1234/draft7/locationIndependentIdentifier.json#/definitions/refToInteger" }, "tests": [ {