Reputation: 12747
I'm looking to generate a set of specs based off some data I'm pulling from a request. I'd like to dynamically define some specs based off the data I receive.
(def my-key :frame-data/pretty_name) ;;imagine this and the validator aren't hardcoded
(def validator string?)
(s/def my-key validator?) ;; defines my-ns/my-key, instead of `:frame-data/pretty_name.
(s/describe my-key) ;; string? ;;which sorta works, but its looking up `my-ns/my-key` instead of :frame-data/pretty_name
My goal is to have a spec that looks like I wrote:
(s/def :frame-data/pretty_name string?)
I'm new to clojure so I don't have a great idea of how it could be done, but I've tried a few things:
(s/def (eval my-key) validator) ;;Assert Failed: k must be a namespaced keyword or resolveable symbol
(definemacro def-spec [key validator]
'(s/def ~key ~validator))
(def-spec my-key validator) ;; my-ns/my-key ;; returns the same as earlier
and many variations on that, but I'm not sure how defining a spec dynamically can be done but it feels like it should be.
Upvotes: 3
Views: 436
Reputation: 4901
You could do a macroexpand on a clojure.spec.alpha/def
form to see what it expands to:
(macroexpand `(s/def :frame-data/pretty_name string?))
;; => (clojure.spec.alpha/def-impl (quote :frame-data/pretty_name) (quote clojure.core/string?) string?)
Or have a look at the source code of clojure.spec.alpha/def
.
Then write your own macro that doesn't quote the spec key in order for it to get evaluated:
(defmacro defspec [k spec-form]
`(s/def-impl ~k (quote ~spec-form) ~spec-form))
Example:
(defspec my-key validator)
(s/valid? my-key "abc")
;; => true
(s/valid? my-key 123)
;; => false
(s/describe my-key)
;; => validator
If you don't like that (s/describe my-key)
returns validator
, try replacing (quote ~spec-form)
in the macro definition by just ~spec-form
, maybe you will like that better.
Upvotes: 2
Reputation: 12747
I'd love to have a better answer, but it seems like this works, only s/describe
returns validator
instead of string?
(eval `(s/def ~my-key validator)) ;; :frame-data/pretty_name
(s/describe my-key) ;; validator
(s/valid? my-key "some string") ;; true
(s/valid? my-key 123) ;; false
Alternatively a weird looking macro works, but the double tilde looks strange, and it feels weird to use eval:
(defmacro define-spec [spec-key validator]
`(eval `(s/def ~~spec-key validator)))
(define-spec my-key validator) ;; :frame-data/pretty_name
Upvotes: 1