spec-tools
spec-tools copied to clipboard
Encodes fails when using multi-spec
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!