Can anybody explain, what's wrong with the example below?
Why does it throw the StackOverflowError exception?
(s/def ::tag keyword?)
(s/def ::s string?)
(s/def ::n number?)
(s/def ::g
(s/cat :tag (s/? ::tag)
:ex (s/alt :string ::s
:number ::n
:and (s/+ ::g)
)))
(s/conform ::g '["abc"])
Similarly to what Alex Miller points out in this Google Groups discussion, s/+ tries to resolve ::g during the definition.
This should do what you want, I think:
(s/def ::g
(s/spec (s/cat :tag (s/? ::tag)
:ex (s/alt :string ::s
:number ::n
:and ::g))))
; REPL
user=> (s/conform ::g [:foo [:bar "abc"]])
{:ex [:and {:ex [:string "abc"] :tag :bar}] :tag :foo}
Related
I'd like to use clojure spec to build up a set of type constraints that can be aliased or further constrained by other specs.
For example, I might have many fields that all need to be valid sanitized markdown.
The following example works for validation (s/valid?) but not for generation (gen/generate)
(s/def ::sanitized-markdown string?)
(s/def ::instruction-list #(s/valid? ::sanitized-markdown %)) ;; works
(gen/generate (s/gen ::instruction-list)) ;; fails
However (gen/generate (s/gen ::sanitized-markdown)) does work.
Is there a way to extend ::instruction-list from ::sanitized-markdown so that it preserves all behavior?
You can alias another spec by providing it directly to s/def:
(s/def ::instruction-list ::sanitized-markdown)
You can use s/merge when merging map specs and s/and in other cases.
(s/def ::sanitized-markdown string?)
(s/def ::instruction-list (s/and ::sanitized-markdown #(> (count %) 10)))
(s/valid? ::instruction-list "abcd")
;; false
(s/valid? ::instruction-list "abcdefghijkl")
;; true
(gen/generate (s/gen ::instruction-list))
;; "178wzJW3W3zx2G0GJ1931eEeO"
An example with maps
(s/def ::a string?)
(s/def ::b string?)
(s/def ::c string?)
(s/def ::d string?)
(s/def ::first-map (s/keys :opt [::a ::b]))
(s/def ::second-map (s/keys :opt [::c ::d]))
(s/def ::third-map (s/merge ::first-map ::second-map))
(s/valid? ::third-map {:a "1" :d "2"})
;; true
(gen/generate (s/gen ::third-map))
;; {::b "gvQ7DI1kQ9DxG7C4poeWhk553", ::d "9KIp77974TEqs9HCq", ::c "qeSZA8NcYr7UVpJDsA17K"}
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]}
Maybe my question has already been answered but I am stuck with a submap specification.
Imagine I have two possibilities like that
{:type :a
:spec {:name "a"}}
{:type :b
:spec {:id "b"}}
In short: the :spec keys depends on the type. For the type :a, the :spec must contain the field :name and for type :b the spec must contain the field :id.
I tried this:
(s/def ::type keyword?)
(defmulti input-type ::type)
(defmethod input-type :a
[_]
(s/keys :req-un [::name]))
(defmethod input-type :b
[_]
(s/keys :req-un [::id]))
(s/def ::spec (s/multi input-type ::type))
(s/def ::input (s/keys :req-un [::type ::spec]))
This tells me: no method ([:spec nil]).
I think I see why: maybe type is not acccessible.
So I thought to make a multi-spec of a higher level (based on the whole map).
Problem: I do not know how to define :spec based on :type because they have the same name. Do you know how to perform this?
Thanks
(s/def ::type keyword?)
(s/def ::id string?)
(s/def ::name string?)
(s/def :id/spec (s/keys :req-un [::id]))
(s/def :name/spec (s/keys :req-un [::name]))
To accommodate the two different meanings for your :spec map, we can define those in different namespaces: :id/spec and :name/spec. Note that the non-namespace suffix of these keywords are both spec and our keys specs are using un-namespaced keywords. These are "fake" namespaces here, but you could also define these in other, "real" namespaces in your project.
(defmulti input-type :type)
(defmethod input-type :a [_]
(s/keys :req-un [::type :name/spec]))
(defmethod input-type :b [_]
(s/keys :req-un [::type :id/spec]))
(s/def ::input (s/multi-spec input-type :type))
(s/valid? ::input {:type :a, :spec {:name "a"}})
=> true
You can also get samples of this spec:
(gen/sample (s/gen ::input))
=>
({:type :a, :spec {:name ""}}
{:type :b, :spec {:id "aI"}} ...
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]
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]