This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-10-20
Channels
- # announcements (1)
- # babashka (74)
- # beginners (84)
- # bristol-clojurians (3)
- # cider (2)
- # clara (14)
- # cljdoc (18)
- # cljsrn (7)
- # clojure (29)
- # clojure-australia (4)
- # clojure-europe (34)
- # clojure-italy (3)
- # clojure-nl (5)
- # clojure-seattle (1)
- # clojure-uk (33)
- # clojuredesign-podcast (2)
- # clojurescript (33)
- # code-reviews (17)
- # core-async (10)
- # cursive (8)
- # datomic (21)
- # depstar (45)
- # dirac (4)
- # duct (10)
- # emacs (1)
- # fulcro (8)
- # jackdaw (2)
- # jobs (1)
- # kaocha (11)
- # leiningen (2)
- # off-topic (8)
- # pathom (35)
- # pedestal (3)
- # protorepl (13)
- # rdf (39)
- # re-frame (23)
- # reagent (2)
- # releases (1)
- # remote-jobs (6)
- # reveal (2)
- # rewrite-clj (18)
- # shadow-cljs (51)
- # sim-testing (2)
- # spacemacs (2)
- # tools-deps (37)
Yesterday I learned that conforming returns a pair only when the spec is valid. I also learned that tap>
doesn't work in a defmulti for some reason. I'm soliciting comments on the right way of using spec when I have a few alternatives for a piece of data and I want a slightly different function for each one.
(spec/def ::required-data (spec/or :object map?
:singleton (spec/and seq?
#(= 1 (count %)))))
(defmulti requirement (fn [{{body :body} :request}]
(tap> "it's happening")
(tap> (spec/conform ::required-data body))
(first (spec/conform ::required-data body)) ))
(defmethod requirement :object [{{body :body} :request}]
(select-keys body [:name :parent :id]) )
(defmethod requirement :singleton [item]
(requirement (first item)))
tap>
should work in that context -- the defmulti
discriminator is "just code".
s/conform
will produce ::s/invalid
if the data doesn't conform.
Hence the exception you get, since you can't take first
of ::s/invalid
.
I'd probably do
(let [data (spec/conform ::required-data body]
(cond-> data (vector? data) (first)))
Alternately, if one wants to explicitly handle the invalid case, one could consider:
(defmulti requirement
(fn [{{body :body} :request}]
(let [conformed-value (s/conform ::required-data body)]
(if-not (s/invalid? conformed-value)
(first conformed-value)
::error))))
(defmethod requirement :object [{{body :body} :request}]
(select-keys body [:name :parent :id]) )
(defmethod requirement :singleton [item]
(requirement (first item)))
(defmethod requirement ::error [item]
;; return bad request and/or log something
:boo)
Also it looks like the :singleton
method is recursively calling requirement
, and it seems to expect a seq. However the dispatch function expects a request map. This confuses me about the shape of data expected.
(requirement {:request {:body {}}}) ; dispatches as :object
(requirement {:request {:body nil}}) ; dispatches as ::error
(requirement :???) ; what will dispatch as singleton?
I believe the current implementation will always dispatch to invalid case for anything except a map shaped like {:request {:body {}}}
.@U0E9KE222 likely the reason it looks like tap> didn't work is that defmulti has defonce semantics, you need to explicitly destroy a defmulti in order to change it
just running defmulti a second time is a no-op if it already exists
adityaathalye: I believe that the data in question is either a map with those keys, or it is a sequence containing a map with those keys, which is why I think the recursion will work once the discriminator (i needed that word, thanks Sean) is fixed up