In our last post, we looked at
s/and, a way to combine multiple specs into a compound spec. It should come as no surprise that spec also provides
s/or to represent a spec made of two or more alternatives.
For example, in this
::ident spec, we have an
s/or with two choices - a name represented by the
string? predicate or an id represented by the
(s/def ::ident (s/or :name string? :id int?)) (s/valid? ::ident "abc") ;; true (s/valid? ::ident 100) ;; true (s/valid? ::ident :foo) ;; false
The first difference we see from
s/and is that
s/or tags the alternatives using keywords (here
:id). In spec we call these paths. Any time there is a spec with either alternatives or components, the parts will be named with a path name in the form of a keyword. Thus
conform tells you not just that a value conformed but also how it conformed.
The conformed value will tag the result to indicate which path was taken or which component is being returned:
(s/conform ::ident "abc") ;;=> [:name "abc"] (s/conform ::ident 100) ;;=> [:id 100]
The conformed value of
s/or is a map entry, so you can either treat it like a 2-element vector with indexes 0 and 1 or invoke map entry functions like
val on it. The key is the tag (the path) and the value is the conformed value of that path's spec.
(let [conformed (s/conform ::ident "abc")] (prn (key conformed)) ;; :name (prn (val conformed)) ;; "abc" (prn (nth conformed 1))) ;; "abc"
s/or will also create a generator that randomly picks one branch of the
or, then invokes the generator for that branch. Let's use
s/exercise to test the generator and the conformed version of the generator:
user=> (pprint (s/exercise ::ident)) ([0 [:id 0]] [0 [:id 0]] ["" [:name ""]] [2 [:id 2]] ["n" [:name "n"]] ["bvpq" [:name "bvpq"]] [0 [:id 0]] [0 [:id 0]] ["7Hakv8Hr" [:name "7Hakv8Hr"]] [-1 [:id -1]])
To learn more about spec, check out the spec guide or stay tuned for more in this blog series!