1

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.

Punit Naik
  • 515
  • 7
  • 26
  • 3
    You want *all* generated values to have *all* optional keys? What is your use case? I would be surprised if there was a flag for that. – gfredericks Apr 12 '18 at 20:15
  • Yes, whatever values are generated, should have all the keys (including optional). – Punit Naik Apr 12 '18 at 20:16
  • If _all_ the generated values should contain the optional keys, well, then the keys are not optional :-) Or said in another way: they would not test the code path that deals with optional keys not being present. – marco.m Apr 17 '18 at 17:24

2 Answers2

3

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.

Taylor Wood
  • 15,886
  • 1
  • 20
  • 37
0

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)))))
Punit Naik
  • 515
  • 7
  • 26