TankorSmash
TankorSmash

Reputation: 12747

How to define specs dynamically?

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

Answers (2)

Rulle
Rulle

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

TankorSmash
TankorSmash

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

Related Questions