diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c85351..a493e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +- Make the validator configurable via an options map. You can use it to enable [inline dereferencing][inline-deref] by passing `{:dereferencing :inline}` as the second parameter to `validator`/`json-validator`. + +[inline-deref]: (http://json-schema.org/latest/json-schema-core.html#anchor30). + ## 0.3.0 (7.8.2016) - `validator` and `json-validator` now take a `JsonSchemaFactory` as an optional argument. This allows e.g. pre-loading schema definitions. diff --git a/project.clj b/project.clj index f1083d4..1a9dc66 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/scjsv "0.3.0" +(defproject metosin/scjsv "0.4.0-SNAPSHOT" :description "Simple JSON-Schema validator for Clojure" :url "https://github.com/metosin/scjsv" :license {:name "Eclipse Public License" diff --git a/src/scjsv/core.clj b/src/scjsv/core.clj index 89402a2..ed69add 100644 --- a/src/scjsv/core.clj +++ b/src/scjsv/core.clj @@ -1,21 +1,34 @@ (ns scjsv.core (:require [cheshire.core :as c]) - (:import [com.fasterxml.jackson.databind ObjectMapper JsonNode] - [com.github.fge.jsonschema.main JsonSchemaFactory] + (:import [com.fasterxml.jackson.databind JsonNode] [com.github.fge.jackson JsonLoader] + [com.github.fge.jsonschema.main JsonSchemaFactory] + [com.github.fge.jsonschema.core.load Dereferencing] + [com.github.fge.jsonschema.core.load.configuration LoadingConfiguration] [com.github.fge.jsonschema.core.report ListProcessingReport ProcessingMessage] [com.github.fge.jsonschema.main JsonSchema])) +(defn- build-factory + "Creates a JsonSchemaFactory based on the options map." + [{:keys [dereferencing] :or {dereferencing :canonical}}] + (let [dereferencing-mode (case dereferencing + :inline (Dereferencing/INLINE) + :canonical (Dereferencing/CANONICAL)) + loading-config (-> (LoadingConfiguration/newBuilder) + (.dereferencing dereferencing-mode) + (.freeze))] + (-> (JsonSchemaFactory/newBuilder) + (.setLoadingConfiguration loading-config) + (.freeze)))) + (defn- ->json-schema "Creates a JSONSchema instance either from a JSON string or a Clojure Map." - [schema factory] + [schema ^JsonSchemaFactory factory] (let [schema-string (if (string? schema) schema (c/generate-string schema)) - mapper (ObjectMapper.) - schema-object (.readTree ^ObjectMapper mapper ^String schema-string) - json-schema (.getJsonSchema ^JsonSchemaFactory factory ^JsonNode schema-object)] - json-schema)) + schema-object (JsonLoader/fromString schema-string)] + (.getJsonSchema factory schema-object))) (defn- validate "Validates (f data) against a given JSON Schema." @@ -28,23 +41,43 @@ (if (seq errors) (map ->clj errors)))) +(defn- ->factory + "Converts value to a JsonSchemaFactory if it isn't one." + [value] + (cond + (instance? JsonSchemaFactory value) value + (map? value) (build-factory value) + :else (throw (Exception. (str "Don't know how to convert " (pr-str value) + " into a JsonSchemaFactory."))))) + ;; ;; Public API ;; (defn json-validator "Returns a JSON string validator (a single arity fn). - Schema can be given either as a JSON String or a Clojure Map." + Schema can be given either as a JSON String or a Clojure Map. + + To configure the validator, you can pass a JsonSchemaFactory instance or a + options map as the second parameter. See scjsv.core/validator docstring for + the options." ([schema] - (json-validator schema (JsonSchemaFactory/byDefault))) + (json-validator schema (build-factory {}))) ([schema json-schema-factory] - (partial validate (->json-schema schema json-schema-factory)))) + (partial validate (->json-schema schema (->factory json-schema-factory))))) (defn validator "Returns a Clojure data structure validator (a single arity fn). - Schema can be given either as a JSON String or a Clojure Map." + Schema can be given either as a JSON String or a Clojure Map. + + To configure the validator, you can pass a JsonSchemaFactory instance or an + options map as the second parameter. The options map can have the following + keys: + + :dereferencing -- Which dereferencing mode to use. Either :canonical (default) + or :inline." ([schema] - (validator schema (JsonSchemaFactory/byDefault))) + (validator schema (build-factory {}))) ([schema json-schema-factory] - (comp (partial validate (->json-schema schema json-schema-factory)) + (comp (partial validate (->json-schema schema (->factory json-schema-factory))) c/generate-string))) diff --git a/test/scjsv/core_test.clj b/test/scjsv/core_test.clj index 502b970..c5a9183 100644 --- a/test/scjsv/core_test.clj +++ b/test/scjsv/core_test.clj @@ -38,7 +38,7 @@ (validate-with-explicit-factory valid) => nil (validate-with-explicit-factory invalid) =not=> nil - (fact "validation errors are lovey clojure maps" + (fact "validation errors are lovely clojure maps" (validate invalid) => [{:domain "validation" :instance {:pointer "/shipping_address"} @@ -49,3 +49,11 @@ :required ["city" "state" "street_address"] :schema {:loadingURI "#" :pointer "/definitions/address"}}]))) + +(fact "Validating a schema that refers to itself" + (let [schema (slurp (io/resource "scjsv/with_id.json")) + default-validate (v/validator schema) + inline-validate (v/validator schema {:dereferencing :inline}) + valid {:foo "foo"}] + (default-validate valid) =not=> nil + (inline-validate valid) => nil)) diff --git a/test/scjsv/with_id.json b/test/scjsv/with_id.json new file mode 100644 index 0000000..5dec959 --- /dev/null +++ b/test/scjsv/with_id.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "resource:/does/not/exist", + "properties": { + "foo": { + "$ref": "#/definitions/foo" + } + }, + "definitions": { + "foo": { + "enum": ["foo"] + } + } +}