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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With