Fork me on GitHub
#clojure-spec
<
2017-07-03
>
yenda09:07:49

is there a "native" way in spec to specify a map with either key a or b ?

mpenet09:07:42

(s/keys :req-un [(or ::a ::b)]) i think

yenda09:07:57

@mpenet I forgot to mention it is either a or b not the two so currently I'm using a xor

(defn xor [x y]
  (or (and x (not y))
      (and y (not x))))

yenda09:07:04

maybe there is no way around it because it is going against spec to forbid things

mpenet09:07:32

I was about to say that, especially for maps

ikitommi16:07:04

Here too: [metosin/spec-tools "0.3.0"] is out with support for Swagger2. More info at #announcements

joshjones16:07:12

@yenda your xor function used in this way gives you what you want

(s/def ::a (s/nilable neg-int?))
(s/def ::b (s/nilable pos-int?))
(s/def ::x string?)

(s/def ::mapspec (s/keys :req-un [::x] :opt-un [::a ::b]))

(s/def ::abspec #(let [a (find % :a)
                       b (find % :b)]
                   (xor a b)))

(s/def ::finalspec (s/and ::mapspec ::abspec))
using find to ensure that even if a and b are nilable, the xor will still be enforced

joshjones16:07:13

and using opt-un in the mapspec ensures that non-namespaced keywords a and b will still be validated

joshjones16:07:08

(s/valid? ::finalspec {:x "yo" :a 100})
=> false
(s/valid? ::finalspec {:x "yo" :a -100})
=> true
(s/valid? ::finalspec {:x "yo" :a 100})
=> false
(s/valid? ::finalspec {:x "yo" :a -100 :b 100})
=> false
(s/valid? ::finalspec {:x "yo" :a nil :b nil})
=> false
(s/valid? ::finalspec {:x "yo" :b 42})
=> true

joshjones17:07:39

Though it's still preferable to not add a restriction like this, as having both keys should do no harm, it's good to know that spec provides the flexibility for this to work