Use macros with doseq to generate spec

183 views Asked by At

I find myself writing a lot of specs like this:

(s/def ::name string?)
(s/def ::logUri string?)
(s/def ::subnet (s/and string? #(> (count %) 5)))
(s/def ::instanceType string?)
...
(s/def ::key (s/and string? #(> (count %) 5)))
(s/def ::instanceCount string?)
(s/def ::bidPct string?)

I.e. Lots of s/and and s/def. This seems like a waste. So I decided to write a macro that did this for me. Something like:

(defmacro and-spec [validations]
  (doseq [[keyname & funcs] validations]
    `(s/def ~keyname (s/and ~@funcs))))

So I would be able to do something like:

(and-spec [[::name1 [string?]]
           [::name2  [string? #(> (count %) 5)]]])

And this would just do all my s/def stuff for me. Unfortunately, the above macro doesn't work, but I'm not sure why.

(s/valid? ::name1 "asdf")
Execution error at emr-cli.utils/eval6038 (form-init17784784591561795514.clj:1).
Unable to resolve spec: :emr-cli.utils/name1

Smaller versions of this work:

(defmacro small-and-spec-works [key-name validations]
  `(s/def ~key-name (s/and ~@validations)))
=> #'emr-cli.utils/tsmall
(and-spec-small-works ::mykey [string?])
=> :emr-cli.utils/mykey
(s/valid? ::mykey "asdf")
=> true

But the second I introduce a let binding things start getting weird:

(defmacro small-and-spec [validation]
  (let [[key-name & valids] validation]
    `(s/def ~key-name (s/and ~@valids))))
=> #'emr-cli.utils/small-and-spec
(small-and-spec [::mykey2 [string?]])
=> :emr-cli.utils/mykey2
(s/valid? ::mykey2 "asdf")
Execution error (IllegalArgumentException) at emr-cli.utils/eval6012 (form-init17784784591561795514.clj:1).
Key must be integer
  1. How can I make the doseq macro work?
  2. What is going wrong with the small-and-spec that creates the Key must be integer error?
1

There are 1 answers

3
akond On BEST ANSWER
(defmacro and-spec [defs]
    `(do
        ~@(map (fn [[name rest]]
              `(s/def ~name (s/and ~@rest))) defs)))

doseq is for side effects. It always returns nil.