Search code examples
clojureclojure.spec

Combining s/and with s/or in Clojure spec


I want to write a spec for a map that either has the key :rule/children or has two keys - :condition/field and :condition/predicate. This is what I have tried:

(s/keys :req [(s/or :children :rule/children :condition (s/and :condition/field :condition/predicate))])

It results in the error message:

Caused by: java.lang.AssertionError: Assert failed: all keys must be namespace-qualified keywords
(every? (fn* [p1__1917#] (c/and (keyword? p1__1917#) (namespace p1__1917#))) (concat req-keys req-un-specs opt opt-un))

I know that for s/or each path must be named. Here there are two paths - this map can either have :children or be a :condition. It is a condition only if it has the two keys :condition/field and :condition/predicate.


Solution

  • In keys specs you can use plain or and and to do this:

    (s/def ::map-spec
      (s/keys :req [(or :rule/children (and :condition/field :condition/predicate))]))
    
    (s/conform ::map-spec {:rule/children 1}) ;; valid
    (s/conform ::map-spec {:condition/field 1}) ;; invalid
    (s/conform ::map-spec {:condition/field 1 :condition/predicate 2}) ;; valid