Reputation: 515
If I have a spec like
(clojure.spec/def ::person (clojure.spec/keys :req [::name ::address] :opt [::age]))
And when I do
(clojure.spec.gen/generate (clojure.spec/gen ::person))
Is there any way to tell the generator to always consider the optional key(s) when generating data for it?
I know that this could be done with custom generators but I wanted to know if there is a functionality already available for it or maybe a simpler approach which does not involve me defining a custom generator.
Upvotes: 1
Views: 847
Reputation: 515
My approach was to walk through the form (using clojure.spec.alpha/form
) of that spec, merge optional keys into required keys if the spec was created using clojure.spec.alpha/keys
and finally regenerate the spec.
(defn merge-opt-keys
"Merges optional keys into requried keys (for specs which are created using `clojure.spec.alpha/keys`) using a spec's form/description"
[fspec]
(let [keymap (into {} (map (fn [pair] (vec pair)) (partition 2 (rest fspec))))]
(->> (cond-> {}
(contains? keymap :opt)
(assoc :req (vec (concat (keymap :req) (keymap :opt))))
(contains? keymap :opt-un)
(assoc :req-un (vec (concat (keymap :req-un) (keymap :opt-un)))))
(mapcat identity)
(cons 'clojure.spec.alpha/keys))))
(clojure.spec.alpha/def ::name string?)
(clojure.spec.alpha/def ::desc string?)
(clojure.spec.alpha/def ::book (clojure.spec.alpha/keys :req [::name] :opt [:desc]))
(clojure.spec.gen.alpha/generate (clojure.spec.alpha/gen (eval (merge-opt-keys (clojure.spec.alpha/form ::book)))))
Upvotes: 0
Reputation: 16194
I think the short answer to your question is "no" but you could s/merge
your spec with one that requires the optional keys:
(s/def ::name string?)
(s/def ::age pos-int?)
(s/def ::person (s/keys :req [::name] :opt [::age]))
(gen/sample (s/gen ::person)) ;; ::age not always gen'd
(gen/sample ;; ::age always gen'd
(s/gen (s/merge ::person (s/keys :req [::age]))))
And you could write a macro that generates a s/keys
spec w/generator that does this.
Upvotes: 3