metosin / spec-tools

Clojure(Script) tools for clojure.spec

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Encodes fails when using multi-spec

felipegmarques opened this issue · comments

Hello!

Problem

Version: 0.10.2

The following code fails:

(require '[clojure.spec.alpha :as s])
(require '[spec-tools.core :as st])

(def strict-json-transformer (st/type-transformer st/strip-extra-keys-transformer
                                                  st/json-transformer))

(def status-set #{:success :error})
(def reason-set #{:generic-error :some-other-error})

(s/def ::reason (st/spec {:spec reason-set :type :keyword}))
(s/def ::status (st/spec {:spec status-set :type :keyword}))

(defmulti inclusion-result :status)
(defmethod inclusion-result :success [_] (s/keys :req-un [::status]))
(defmethod inclusion-result :error [_] (s/keys :req-un [::status ::reason]))

(s/def ::inclusion-result (s/multi-spec inclusion-result :status))
(st/encode ::inclusion-result {:status :error, :reason :some-other-error} strict-json-transformer)

with the following exception:

1. Unhandled java.lang.IllegalStateException
   No method of: com.piposaude.spec-serialization.core-test/inclusion-result for
   dispatch value: error

                 alpha.clj:  964  clojure.spec.alpha/multi-spec-impl/reify
                 alpha.clj:  171  clojure.spec.alpha/unform
                 alpha.clj:  166  clojure.spec.alpha/unform
                 core.cljc:  417  spec_tools.core.Spec/unform_STAR_
                 alpha.clj:  171  clojure.spec.alpha/unform
                 alpha.clj:  166  clojure.spec.alpha/unform
                 core.cljc:  259  spec_tools.core$encode/invokeStatic
                 core.cljc:  250  spec_tools.core$encode/invoke
                 ....

I believe this relates to #119 .

Looking the code I saw that in the encode function there is an conform + unform pair, but the data
being passed to the unform is {:error "error" :status "some-other-error"}, therefore the multi-spec fails to perform the dispatch and find the right spec to do the unform.

Possible solution?

In the issue #119, you guys mention the possibility of:

if there is a way to ask from a multi-spec what spec it will dispatch for this given data, we could unform against that spec
directly, not for the multispec itself.

Which I imagine maps to these lines of code in the spec source-code (ref1 and ref2), wheremm is the multi-method.

(let [predx #(let [^clojure.lang.MultiFn mm @mmvar]
                    (c/and (.getMethod mm ((.dispatchFn mm) %))
                           (mm %)))]
; ....
(unform* [_ x] (if-let [pred (predx x)]
                         (unform pred x)
                         (throw (IllegalStateException. (str "No method of: " form " for dispatch value: " (dval x))))))

I believe it would be possible to modify the encode to get the right spec to use in the unform in the case of multi-spec.
But I'm not sure if this would be the right place to do so and how it would impact when we merge multi-spec with others specs. Maybe it would be better to wrap the multi-spec in a st/spec and modify the unform* there, but probably it would need access to the original data.

What do you think? Would be ok to add this as a condition in the encode function?
If you guys could give me direction on the best way to proceed, I could try to submit a patch.

Thanks!