Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ClassCastException using transformers #250

Open
wandersoncferreira opened this issue Dec 5, 2020 · 0 comments
Open

ClassCastException using transformers #250

wandersoncferreira opened this issue Dec 5, 2020 · 0 comments

Comments

@wandersoncferreira
Copy link
Contributor

wandersoncferreira commented Dec 5, 2020

Hello @miikka I am opening this issue to discuss the exception I mentioned on PR #248 . I did some further exploration around this problem and there are 2 (at least) different scenarios going on.

  1. The Encoder/Decoder functions should handle their own exceptions?
  2. Calls to s/unform using values that are not compliant with s/conform.

Let's follow an example.

(s/def ::my-spec
  (spec
   {:spec #(and (simple-keyword? %) (-> % name clojure.string/lower-case keyword (= %)))
    :description "a lowercase keyword, encoded in uppercase in string-mode"
    :encode/string (fn [spec value]
                     (println value) ;; => :wand or ;; => "wand"
                     (-> value name clojure.string/upper-case))}))

;; this call produces an ::s/invalid result from s/conform however, as
;; this is a leaf node the conformer let this pass through.
(encode ::my-spec :wand string-transformer)
;; => "WAND"

The :wand keyword comply with the provided spec, but the transformed value does not ("WAND").

;; this call is problematic (in some sense) because "WAND" does not
;; comply with s/conform therefore we are "stretching" s/unform a
;; little bit here. This is a gray area for me yet, what is the
;; expected output for these situations.
(s/unform ::my-spec "WAND")
;; => "WAND"

But luckly (for me) it does not produce error.

Then we can see now the two sources of problems. When we try to encode a value that produces an error inside our encoder function, we get the cryptic message:

(encode ::my-spec {:error-expected "wand"} string-transformer)

;; 1. Unhandled java.lang.ClassCastException
;;    (No message)

The error comes from here: (-> {:error-expected "wand"} name clojure.string/upper-case).

Potential solutions:

  1. Improve documentation to explicitly guide users to handle their exceptions e.g. the spec-tools.transform namespace clearly handles all the possible exceptions for each transformation that can throw some error. The guideline is "if you don't know how to transform it, return as is".

  2. Catch errors from transform call during the conform step and ensure the previous behavior for the user. Something like this would be enough:

;;; spec-tools.core L404
(let [transformer *transformer*, encode? *encode?*
      safe-transform (fn [t this x] (try (t this x) (catch Throwable _ x)))]
      ;; if there is a transformer present
      (if-let [transform (if transformer ((if encode? -encoder -decoder) transformer (decompose-spec-type this) x))]
        ;; let's transform it
        (let [transformed (safe-transform transform this x)]
          ...))

The other scenario is about calling s/unform with not compliant values becomes problem when this operation is not allowed. For example, the case motivating the PR #248 returns a string and when we call s/unform with the string we get another ClassCastException.

(s/def :db/hostname string?)
(s/def :db/port pos-int?)
(s/def :db/database string?)
(s/def ::jdbc-connection
  (spec {:spec (s/keys :req-un [:db/hostname :db/port :db/database])
         :type :dbconn}))

(s/unform ::jdbc-connection "jdbc:....")

I think solving this one is a lot harder, because conform* is returning values that are not valid (

;; it's ok if encode transforms leaf values into invalid values
) and if we remove this, examples like the first one I brought here would start breaking.

Idk, maybe wrap this to catch errors and default to doing nothing if error occur.

Would like to hear more from you about this.
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant