Skip to content

Commit 826e1c6

Browse files
authored
Merge pull request #279 from jimpil/master
add support for or/and key-groups in s/keys
2 parents 04da380 + 139b600 commit 826e1c6

File tree

2 files changed

+47
-12
lines changed

2 files changed

+47
-12
lines changed

src/spec_tools/json_schema.cljc

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,19 +184,55 @@
184184
(assoc schema :title (impl/qualified-name title))
185185
schema)))
186186

187+
(def key-group-mapping
188+
{'or :anyOf ;; there is no 'xor' key-group, so 'anyOf' is more appropriate than 'oneOf'
189+
'and :allOf})
190+
191+
(defn- parse-required1
192+
"Helper for generating correct schemas for :req/:req-un keys,
193+
taking into account potential or/and key-goups."
194+
[name-fn x]
195+
(if (list? x) ;; found key-group
196+
(let [k (or (key-group-mapping (first x))
197+
(throw
198+
(ex-info "unsupported key-group expression" {:expression (first x)})))
199+
v (mapv (partial parse-required1 name-fn) (next x))]
200+
{k (if (and (= k :allOf)
201+
(every? :required v))
202+
[{:required (into [] (mapcat :required) v)}]
203+
v)})
204+
{:required [(name-fn x)]}))
205+
206+
(def parse-req* (partial parse-required1 impl/qualified-name))
207+
(def parse-req-un* (partial parse-required1 name))
208+
209+
(comment
210+
211+
(parse-req-un* '(or :foo (and :bar :baz)))
212+
;; =>
213+
{:anyOf [{:required ["foo"]}
214+
{:allOf [{:required ["bar"]}
215+
{:required ["baz"]}]}]}
216+
)
217+
187218
(defmethod accept-spec 'clojure.spec.alpha/keys [_ spec children options]
188-
(let [{:keys [req req-un opt opt-un]} (impl/parse-keys (impl/extract-form spec))
219+
(let [form (impl/extract-form spec)
220+
{:keys [req req-un opt opt-un]} (impl/parse-keys form)
189221
names-un (map name (concat req-un opt-un))
190222
names (map impl/qualified-name (concat req opt))
191-
required (map impl/qualified-name req)
192-
required-un (map name req-un)
223+
m (some->> form (rest) (apply hash-map))
224+
required (map parse-req* (:req m))
225+
required-un (map parse-req-un* (:req-un m))
193226
all-required (not-empty (concat required required-un))]
194227
(maybe-with-title
195228
(merge
196229
{:type "object"
197230
:properties (zipmap (concat names names-un) children)}
198231
(when all-required
199-
{:required (vec all-required)}))
232+
(if (every? :required all-required)
233+
;; avoid changing the simple case & break existing tests
234+
{:required (into [] (mapcat :required) all-required)}
235+
{:allOf (vec all-required)})))
200236
spec
201237
options)))
202238

test/cljc/spec_tools/json_schema_test.cljc

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,13 @@
9090
"c" {:type "string"}
9191
"d" {:type "string"}
9292
"e" {:type "string"}}
93-
:required ["spec-tools.json-schema-test/a"
94-
"spec-tools.json-schema-test/b"
95-
"spec-tools.json-schema-test/c"
96-
"spec-tools.json-schema-test/d"
97-
"a"
98-
"b"
99-
"c"
100-
"d"]}))
93+
:allOf [{:required ["spec-tools.json-schema-test/a"]}
94+
{:anyOf [{:required ["spec-tools.json-schema-test/b"]}
95+
{:allOf [{:required ["spec-tools.json-schema-test/c"
96+
"spec-tools.json-schema-test/d"]}]}]}
97+
{:required ["a"]}
98+
{:anyOf [{:required ["b"]}
99+
{:allOf [{:required ["c" "d"]}]}]}]}))
101100
(is (= (jsc/transform ::keys-no-req)
102101
{:type "object"
103102
:title "spec-tools.json-schema-test/keys-no-req"

0 commit comments

Comments
 (0)