How to always generate data for optional keys in a spec?

842 Views Asked by At

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.

2

There are 2 best solutions below

0
On BEST ANSWER

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)))))
0
On

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.