clojureclojure.spec

clojure spec: map containing either a :with or a :height (XOR)


The following clojure spec ::my permits maps having either the key :width or the key :height, however it does not permit having both of them:

(s/def ::width int?)

(s/def ::height int?)

(defn one-of-both? [a b]
  (or (and a (not b))
      (and b (not a))))

(s/def ::my (s/and (s/keys :opt-un [::width ::height])
                   #(one-of-both? (% :width) (% :height))))

Even if it does the job:

(s/valid? ::my {})
false
(s/valid? ::my {:width 5})
true
(s/valid? ::my {:height 2})
true
(s/valid? ::my {:width 5 :height 2})
false

the code does not appear that concise to me. First the keys are defined as optional and then as required. Does anyone have a more readable solution to this?


Solution

  • clojure.spec is designed to encourage specs capable of growth. Thus its s/keys does not support forbidding keys. It even matches maps having keys that are neither in :req or opt.

    There is however a way to say the map must at least have :width or :height, i.e. not XOR, just OR.

    (s/def ::my (s/keys :req-un [(or ::width ::height)]))