ClojureDocs

Nav

Namespaces

multi-spec

  • (multi-spec mm retag)
Takes the name of a spec/predicate-returning multimethod and a
tag-restoring keyword or fn (retag).  Returns a spec that when
conforming or explaining data will pass it to the multimethod to get
an appropriate spec. You can e.g. use multi-spec to dynamically and
extensibly associate specs with 'tagged' data (i.e. data where one
of the fields indicates the shape of the rest of the structure).
 (defmulti mspec :tag)
 The methods should ignore their argument and return a predicate/spec:
(defmethod mspec :int [_] (s/keys :req-un [::tag ::i]))
 retag is used during generation to retag generated values with
matching tags. retag can either be a keyword, at which key the
dispatch-tag will be assoc'ed, or a fn of generated value and
dispatch-tag that should return an appropriately retagged value.
 Note that because the tags themselves comprise an open set,
the tag key spec cannot enumerate the values, but can e.g.
test for keyword?.
 Note also that the dispatch values of the multimethod will be
included in the path, i.e. in reporting and gen overrides, even
though those values are not evident in the spec.
2 Examples
Use retag function to allow generative testing of multi-spec whose
multimethod has a :default dispatch method.

(require '[clojure.spec.alpha :as s]
         '[clojure.spec.gen.alpha :as gen])

(s/def ::tag #{:a :b :c :d})
(s/def ::example-key keyword?)
(s/def ::different-key keyword?)

(defmulti tagmm :tag)
(defmethod tagmm :a [_] (s/keys :req-un [::tag ::example-key]))
(defmethod tagmm :default [_] (s/keys :req-un [::tag ::different-key]))

(s/def ::example (s/multi-spec tagmm :tag))
(gen/sample (s/gen ::example))
;=> only get examples with :tag :a

(s/def ::example (s/multi-spec tagmm (fn retag [gen-v dispatch-tag] gen-v)))
(gen/sample (s/gen ::example))
;=> get examples with all :tag options
(def create-playlist (s/cat :command ::commands :type ::types :data (s/coll-of ::playlist)))
(def create-song (s/cat :command ::commands :type ::types :data (s/coll-of ::song)))
(def create-user (s/cat :command ::commands :type ::types :data (s/coll-of ::user)))

(defmulti create-spec? second)
(defmethod create-spec? :playlist [_] create-playlist)
(defmethod create-spec? :song [_] create-song)
(defmethod create-spec? :user [_] create-user)

(def update-playlist (s/cat :command ::commands :type ::types :data (s/coll-of ::playlist-update)))
(def update-song (s/cat :command ::commands :type ::types :data (s/coll-of ::song-update)))
(def update-user (s/cat :command ::commands :type ::types :data (s/coll-of ::user-update)))

(defmulti update-spec? second)
(defmethod update-spec? :playlist [_] update-playlist)
(defmethod update-spec? :song [_] update-song)
(defmethod update-spec? :user [_] update-user)

;; [:create :user [{:name "bobby"} {:name "sally"}{:name "fred"}]]
(s/def ::create-action
  (s/with-gen  #(s/multi-spec create-spec? %)
    #(gen/fmap vec (s/gen create-action-pattern))))

;; [:update :user ["1"] [{:name "bobby minerva"}]]
(s/def ::update-action
  (s/with-gen #(s/multi-spec update-spec? %)
    #(gen/fmap vec (s/gen update-action-pattern))))

;; examples
=> (s/explain ::create-action [:create :user [{:name "bobby"} {:name "sally"}{:name "fred"}]])
Success!
nil

=> (s/explain ::update-action [:update :user ["1"] [{:name "bobby minerva"}]])
Success!
nil
See Also
No see-alsos for clojure.spec.alpha/multi-spec
0 Notes
No notes for multi-spec