How to define Clojure spec for `'(foo (:x 1 :y 2))` - clojure

The:
(s/def ::a (s/cat :k keyword? :i int?))
(s/def ::b (s/cat :symbol any?
:a (s/coll-of ::a)))
specs:
(s/conform ::b '(foo ((:x 1) (:y 2))))
The:
(s/def ::a (s/cat :k keyword? :i int?))
(s/def ::b (s/cat :symbol any?
:a (s/* ::a)))
specs:
(s/conform ::b '(foo :x 1 :y 2))
but how do I spec (s/conform ::b '(foo (:x 1 :y 2))) ?

To nest that into a list, you need to wrap it in s/spec. E.g.:
(s/def ::b (s/cat :symbol any? :a (s/spec (s/* ::a))))
This is mentioned in the Spec Guide:
When regex ops are combined, they describe a single sequence. If you need to spec a nested sequential collection, you must use an explicit call to spec to start a new nested regex context. For example to describe a sequence like [:names ["a" "b"] :nums [1 2 3]], you need nested regular expressions to describe the inner sequential data:
(s/def ::nested
(s/cat :names-kw #{:names}
:names (s/spec (s/* string?))
:nums-kw #{:nums}
:nums (s/spec (s/* number?))))
(s/conform ::nested [:names ["a" "b"] :nums [1 2 3]])
;;=> {:names-kw :names, :names ["a" "b"], :nums-kw :nums, :nums [1 2 3]}

Related

Clojure spec maps

Having two following specs:
(s/def ::x keyword?)
(s/def ::y keyword?)
(s/def ::z keyword?)
(s/def ::a
(s/keys :req-un [::x
::y]
:opt-un [::z]))
(s/def ::b
(s/map-of string? string?))
how do I combine ::a and ::b into ::m so the following data is valid:
(s/valid? ::m
{:x :foo
:y :bar
:z :any})
(s/valid? ::m
{:x :foo
:y :bar})
(s/valid? ::m
{:x :foo
:y :bar
:z :baz})
(s/valid? ::m
{:x :foo
:y :bar
:z "baz"})
(s/valid? ::m
{:x :foo
:y :bar
:t "tic"})
additionally, how do I combine ::a and ::b into ::m so the following data is invalid:
(s/valid? ::m
{"r" "foo"
"t" "bar"})
(s/valid? ::m
{:x :foo
"r" "bar"})
(s/valid? ::m
{:x :foo
:y :bar
:r :any})
Neither of :
(s/def ::m (s/merge ::a ::b))
(s/def ::m (s/or :a ::a :b ::b))
works (as expected), but is there a way to match map entries in priority of the spec order?
The way it should work is the following:
take all the map entries of the value (which is a map)
partition the map entries into two sets. One confirming the ::a spec and the other conforming the ::b spec.
The two sub-maps should conform each the relevant spec as a whole. E.g the first partition should have all the required keys.
You can do this by treating the map not as a map but as a collection of map entries, and then validate the map entries. Handling the "required" keys part has to be done by s/and'ing an additional predicate.
(s/def ::x keyword?)
(s/def ::y keyword?)
(s/def ::z keyword?)
(s/def ::entry (s/or :x (s/tuple #{::x} ::x)
:y (s/tuple #{::y} ::y)
:z (s/tuple #{::z} ::z)
:str (s/tuple string? string?)))
(defn req-keys? [m] (and (contains? m :x) (contains? m :y)))
(s/def ::m (s/and map? (s/coll-of ::entry :into {}) req-keys?))

How to create a spec where all keys are optional but at least one of the specified keys should be present?

How am I supposed to create a spec where all keys are optional but at least one of the specified keys should be present?
(s/def ::my-spec (s/and (help-plz??)(s/keys :opt-un [::a ::b])))
(s/valid? ::my-spec {} => false
(s/valid? ::my-spec {:a 1}) => true
(s/valid? ::my-spec {:b 1}) => true
(s/valid? ::my-spec {:a 1 :b 1}) => true
(s/valid? ::my-spec {:A1 :B 1}) => true
With the current spec alpha, in order to use the same key collection for both the keys spec and the at-least-one-exists check, you'll need to use a macro. (The upcoming spec 2 alpha addresses this by exposing more data-driven APIs for creating specs.)
Here's a quick sketch for your particular example:
(defmacro one-or-more-keys [ks]
(let [keyset (set (map (comp keyword name) ks))]
`(s/and (s/keys :opt-un ~ks)
#(some ~keyset (keys %)))))
(s/def ::my-spec (one-or-more-keys [::foo ::bar]))
(s/conform ::my-spec {:bar nil})
=> {:bar nil}
(s/conform ::my-spec {:baz nil})
=> :clojure.spec.alpha/invalid
Alternatively, you could just define the key collection twice, and use a similar predicate with s/and.
Per the docs for keys:
The :req key vector supports 'and' and 'or' for key groups:
(s/keys :req [::x ::y (or ::secret (and ::user ::pwd))] :opt [::z])
Your code should be:
(s/def ::my-spec (s/keys :req-un [(or ::a ::b)]))

Use Clojure Spec to check type consistency on a cons?

If I run a library with borrowers and books:
(s/def ::brs (s/coll-of ::br/borrower))
(s/def ::bks (s/coll-of ::bk/book))
And I want a generic function that adds an item to either collection:
(defn add-item [x xs]
(if (some #{x} xs)
xs
(cons x xs)))
How do I write a spec that makes sure I can't add a book to borrowers and vice versa?
Because this spec:
(s/fdef add-item
:args (s/fspec :args (s/or :is-brs (s/and (s/cat :x ::br/borrower) (s/cat :xs ::brs))
:is-bks (s/and (s/cat :x ::bk/book) (s/cat :xs ::bks))))
:ret (s/or :ret-brs ::brs
:ret-bks ::bks))
is not working. :-(
Thank you for your help!
Using int/string definitions for ::brs and ::bks:
(s/def ::brs (s/coll-of int?))
(s/def ::bks (s/coll-of string?))
This should work:
(s/fdef add-item
:args (s/or
:brs (s/cat :x int? :xs ::brs)
:bks (s/cat :x string? :xs ::bks))
:ret (s/or :brs ::brs
:bks ::bks))
;; instrument function here
(add-item 1 [2])
=> (1 2)
(add-item "1" [2]) ;; throws exception

In clojure.spec, what is the difference between `s/or` and `s/alt`? [duplicate]

I'm following clojure.spec's guide (http://clojure.org/guides/spec). I'm confused by the difference between alt and or for sequence spec.
For me the two following examples work equally well. So what's the difference between the two?
; Use `alt`
(s/def ::config (s/* (s/cat :prop string?
:val (s/alt :s string? :b boolean?))))
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
; Use `or`
(s/def ::config (s/* (s/cat :prop string?
:val (s/or :s string? :b boolean?))))
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
s/alt is for concatenating nested regex specs where using s/or specifies a nested sequence. In your example it doesn't make a difference since you are not using nested regex specs. Here is an example:
(s/def ::number-regex (s/* number?))
(s/def ::or-example (s/cat :nums (s/or :numbers ::number-regex)))
(s/valid? ::or-example [1 2 3])
;;-> false
(s/valid? ::or-example [[1 2 3]])
;;-> true
As you can see, or specifies a nested sequence in which a new regex context is started, whereas alt specifies the opposite:
(s/def ::alt-example (s/cat :nums (s/alt :numbers ::number-regex)))
(s/valid? ::alt-example [1 2 3])
;;-> true
(s/valid? ::alt-example [[1 2 3]])
;;-> false
From http://clojure.org/guides/spec, we know
When regex ops are combined, they describe a single sequence.
that means if you want to valid the nested sequences, you should do like this.
(s/def ::config (s/*
(s/cat :prop string?
:val (s/spec
(s/alt :s string? :b #(instance? Boolean %))))))
And then your data looks like this (Notice the brackets around)
(s/explain ::config ["-server" ["foo"] "-verbose" [true] "-user" [13]])
Also, if you do (s/or).
(s/def ::config (s/* (s/cat :prop string?
:val (s/spec
(s/or :s string? :b #(instance? Boolean %))))))
your data should be the same as the old one (Notice there are no brackets around)
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
BTW, for non-nested sequences. there is still a little bit difference between (s/alt ) and (s/or):
;;; for (s/or)
(s/def ::name-or-id (s/or :name string?
:id int?))
(s/conform ::name-or-id 42) ;;=> [:id 42]
;;; for (s/alt)
(s/def ::name-or-id (s/alt :name string?
:id int?))
(s/conform ::name-or-id [42]) ;;=> [:id 42]

clojure.spec: `alt` vs `or` for sequence spec

I'm following clojure.spec's guide (http://clojure.org/guides/spec). I'm confused by the difference between alt and or for sequence spec.
For me the two following examples work equally well. So what's the difference between the two?
; Use `alt`
(s/def ::config (s/* (s/cat :prop string?
:val (s/alt :s string? :b boolean?))))
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
; Use `or`
(s/def ::config (s/* (s/cat :prop string?
:val (s/or :s string? :b boolean?))))
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
s/alt is for concatenating nested regex specs where using s/or specifies a nested sequence. In your example it doesn't make a difference since you are not using nested regex specs. Here is an example:
(s/def ::number-regex (s/* number?))
(s/def ::or-example (s/cat :nums (s/or :numbers ::number-regex)))
(s/valid? ::or-example [1 2 3])
;;-> false
(s/valid? ::or-example [[1 2 3]])
;;-> true
As you can see, or specifies a nested sequence in which a new regex context is started, whereas alt specifies the opposite:
(s/def ::alt-example (s/cat :nums (s/alt :numbers ::number-regex)))
(s/valid? ::alt-example [1 2 3])
;;-> true
(s/valid? ::alt-example [[1 2 3]])
;;-> false
From http://clojure.org/guides/spec, we know
When regex ops are combined, they describe a single sequence.
that means if you want to valid the nested sequences, you should do like this.
(s/def ::config (s/*
(s/cat :prop string?
:val (s/spec
(s/alt :s string? :b #(instance? Boolean %))))))
And then your data looks like this (Notice the brackets around)
(s/explain ::config ["-server" ["foo"] "-verbose" [true] "-user" [13]])
Also, if you do (s/or).
(s/def ::config (s/* (s/cat :prop string?
:val (s/spec
(s/or :s string? :b #(instance? Boolean %))))))
your data should be the same as the old one (Notice there are no brackets around)
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
BTW, for non-nested sequences. there is still a little bit difference between (s/alt ) and (s/or):
;;; for (s/or)
(s/def ::name-or-id (s/or :name string?
:id int?))
(s/conform ::name-or-id 42) ;;=> [:id 42]
;;; for (s/alt)
(s/def ::name-or-id (s/alt :name string?
:id int?))
(s/conform ::name-or-id [42]) ;;=> [:id 42]