Skip to content

Commit

Permalink
Merge pull request #80 from metosin/ParseKeySpecsFix
Browse files Browse the repository at this point in the history
Ensure and and or are parsed correctly from key specs
  • Loading branch information
ikitommi authored Oct 10, 2017
2 parents 5e0a94d + 1e591c8 commit 1fea5c3
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 20 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
## 0.4.0-SNAPSHOT

* `or` and `and` keys are parsed correctly for JSON Schema & Swagger, Fixes [#79](https://github.com/metosin/spec-tools/issues/79)
* **BREAKING**: `spec-tools.type` is now `spec-tools.parse` with public api of:
* `parse-spec`: given a spec name, form or instance, maybe returns a spec info map with resolved `:type` and optionally other info, e.g. `:keys` for `s/keys` specs.
* `parse-spec`: given a spec name, form or instance, maybe returns a spec info map with resolved `:type` and optionally other info, e.g. `:keys`, `:keys/req` and `:keys/opt` for `s/keys` specs.
* `parse-form`: multimethod to parse info out of a form
* Spec Records of `s/and` are fully resolved now, fixes https://github.com/metosin/compojure-api/issues/336

Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ The following Spec keys having a special meaning:
| `:name` | Name of the spec. Maps to `title` in JSON Schema. |
| `:description` | Description of the spec. Maps to `description` in JSON Schema. |
| `:gen` | Generator function for the Spec (set via `s/with-gen`) |
| `:keys`           | Set of map keys that the spec defines. Extracted from `s/keys` Specs.       |
| `:keys`           | Set of all map keys that the spec defines. Extracted from `s/keys` Specs.   |
| `:keys/req`       | Set of required map keys that the spec defines. Extracted from `s/keys` Specs.|
| `:keys/opt`       | Set of optional map keys that the spec defines. Extracted from `s/keys` Specs.|
| `:reason` | Value is added to `s/explain-data` problems under key `:reason` |
| `:json-schema/...` | Extra data that is merged with unqualifed keys into json-schema |

Expand Down Expand Up @@ -462,7 +464,8 @@ A tool to walk over and transform specs using the [Visitor-pattern](https://en.w
; {:spec (clojure.spec.alpha/keys
; :req-un [:user$person$orders/id :user$person$orders/description])
; :type :map
; :keys #{:id :description}})
; :keys #{:id :description}
; :keys/req #{:id :description}})
; :into [])
; :type :vector})
; :user$person$address/street (spec-tools.core/spec
Expand All @@ -477,7 +480,8 @@ A tool to walk over and transform specs using the [Visitor-pattern](https://en.w
; {:spec (clojure.spec.alpha/keys
; :req-un [:user$person$address/street :user$person$address/zip])
; :type :map
; :keys #{:street :zip}}))
; :keys #{:street :zip}
; :keys/req #{:street :zip}}))
; :type nil})
; :user$person/description (spec-tools.core/spec
; {:spec clojure.core/string?
Expand Down
9 changes: 8 additions & 1 deletion src/spec_tools/impl.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,14 @@
:else x))

(defn polish-un [x]
(-> x polish name keyword))
(some-> x polish name keyword))

(defn parse-keys [form]
(let [m (some->> form (rest) (apply hash-map))]
(cond-> m
(:req m) (update :req #(->> % flatten (keep polish) (into [])))
(:req-un m) (update :req-un #(->> % flatten (keep polish-un) (into [])))
(:opt-un m) (update :opt-un #(->> % (keep polish-un) (into []))))))

(defn extract-keys [form]
(let [{:keys [req opt req-un opt-un]} (some->> form (rest) (apply hash-map))]
Expand Down
2 changes: 1 addition & 1 deletion src/spec_tools/json_schema.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@
schema))

(defmethod accept-spec 'clojure.spec.alpha/keys [_ spec children _]
(let [[_ & {:keys [req req-un opt opt-un]}] (impl/extract-form spec)
(let [{:keys [req req-un opt opt-un]} (impl/parse-keys (impl/extract-form spec))
names-un (map name (concat req-un opt-un))
names (map impl/qualified-name (concat req opt))
required (map impl/qualified-name req)
Expand Down
12 changes: 5 additions & 7 deletions src/spec_tools/parse.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,11 @@
(defmethod parse-form :clojure.spec.alpha/unknown [_ _])

(defmethod parse-form 'clojure.spec.alpha/keys [_ form]
(let [{:keys [req opt req-un opt-un]} (some->> form (rest) (apply hash-map))]
{:type :map
:keys (set
(flatten
(concat
(map impl/polish (concat req opt))
(map impl/polish-un (concat req-un opt-un)))))}))
(let [{:keys [req opt req-un opt-un]} (impl/parse-keys form)]
(cond-> {:type :map
:keys (set (concat req opt req-un opt-un))}
(or req req-un) (assoc :keys/req (set (concat req req-un)))
(or opt opt-un) (assoc :keys/opt (set (concat opt opt-un))))))

(defmethod parse-form 'clojure.spec.alpha/or [_ _])

Expand Down
7 changes: 5 additions & 2 deletions test/cljc/spec_tools/core_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,9 @@

(testing "all keys types are extracted"
(is (= {:type :map
:keys #{::age :lat ::truth :uuid}}
:keys #{::age :lat ::truth :uuid}
:keys/req #{::age :lat}
:keys/opt #{::truth :uuid}}

;; named spec
(info/parse-spec
Expand All @@ -432,7 +434,8 @@

(testing "ands and ors are flattened"
(is (= {:type :map
:keys #{::age ::lat ::uuid}}
:keys #{::age ::lat ::uuid}
:keys/req #{::age ::lat ::uuid}}
(info/parse-spec
(s/keys
:req [(or ::age (and ::uuid ::lat))]))))))
Expand Down
36 changes: 31 additions & 5 deletions test/cljc/spec_tools/json_schema_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@
(s/def ::integer integer?)
(s/def ::string string?)
(s/def ::set #{1 2 3})
(s/def ::keys (s/keys :req-un [::integer]))

(s/def ::a string?)
(s/def ::b string?)
(s/def ::c string?)
(s/def ::d string?)
(s/def ::e string?)

(s/def ::keys (s/keys :opt [::e]
:opt-un [::e]
:req [::a (or ::b (and ::c ::d))]
:req-un [::a (or ::b (and ::c ::d))]))

(deftest simple-spec-test
(testing "primitive predicates"
Expand All @@ -39,10 +49,26 @@
:properties {"integer" {:type "integer"} "string" {:type "string"}}
:required ["integer"]}))
(is (= (jsc/transform ::keys)
{:type "object",
:properties {"integer" {:type "integer"}},
:required ["integer"],
:title "spec-tools.json-schema-test/keys"}))
{:type "object"
:title "spec-tools.json-schema-test/keys"
:properties {"spec-tools.json-schema-test/a" {:type "string"}
"spec-tools.json-schema-test/b" {:type "string"}
"spec-tools.json-schema-test/c" {:type "string"}
"spec-tools.json-schema-test/d" {:type "string"}
"spec-tools.json-schema-test/e" {:type "string"}
"a" {:type "string"}
"b" {:type "string"}
"c" {:type "string"}
"d" {:type "string"}
"e" {:type "string"}}
:required ["spec-tools.json-schema-test/a"
"spec-tools.json-schema-test/b"
"spec-tools.json-schema-test/c"
"spec-tools.json-schema-test/d"
"a"
"b"
"c"
"d"]}))
(is (= (jsc/transform (s/or :int integer? :string string?))
{:anyOf [{:type "integer"} {:type "string"}]}))
(is (= (jsc/transform (s/and integer? pos?))
Expand Down
22 changes: 22 additions & 0 deletions test/cljc/spec_tools/parse_test.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(ns spec-tools.parse-test
(:require [clojure.test :refer [deftest is]]
[spec-tools.parse :as parse]
[clojure.spec.alpha :as s]))

(s/def ::a string?)
(s/def ::b string?)
(s/def ::c string?)
(s/def ::d string?)
(s/def ::e string?)

(s/def ::keys (s/keys :opt [::e]
:opt-un [::e]
:req [::a (or ::b (and ::c ::d))]
:req-un [::a (or ::b (and ::c ::d))]))

(deftest parse-test
(is (= {:type :map
:keys #{:a :b :c :d :e ::a ::b ::c ::d ::e}
:keys/req #{:a :b :c :d ::a ::b ::c ::d}
:keys/opt #{:e ::e}}
(parse/parse-spec ::keys))))

0 comments on commit 1fea5c3

Please sign in to comment.