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)))
Don't know how to create ISeq from: clojure.lang.Keyword
is what tipped me off that, oh, I guess there are other cases for my data
I had thought that conform would throw
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 {}}}
.@mathpunk 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
I definitely just put it in and reevaluated
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
but! part of the job of this code is to warn me if that belief is incorrect
so I will write something that handles the invalid case
sean: my first cond->
! I was just about to ask you the rationale for using it over an if-statement, but it became clear once I actually started writing it out 🙂
thanks folks!