Malli customer transformers

Hi all!

I’m using Malli to decode and validate some data containing datetimes, and I’m a bit stuck trying to figure out how to write a customer transformer to deal with them. I’m using tick to deal with the datetimes themselves.

Here’s what I’ve got so far:

(require '[malli.core :as m]
         '[malli.transform :as mt]
         '[tick.core :as t])

;; Malli transformer for datetime strings; see
;; https://github.com/metosin/malli/issues/49#issuecomment-763957635
(def date-time-transformer
  (mt/transformer
   {:encoders {:date-time {:compile (fn [_ _] {:enter str})}}}
   {:decoders {:date-time {:compile (fn [_ _] {:enter t/date-time})}}}))

(def schema-registry
  {:registry {:date-time (m/-simple-schema {:pred t/date-time?
                                            :type :date-time})}})

(m/decode :date-time
          "2021-03-29T10:00:00"
          schema-registry
          date-time-transformer)
;; => #time/date-time "2021-03-29T10:00"

(m/encode :date-time
          #time/date-time "2021-03-29T10:00:00"
          schema-registry
          date-time-transformer)
;; => "2021-03-29T10:00"

All good! But when I try sticking my datetime in a vector:

(m/decode [:catn [:timestamp :date-time]]
          ["2021-03-29T10:00:00"]
          schema-registry
          date-time-transformer)

I get an exception:

1. Unhandled clojure.lang.ExceptionInfo
   :malli.core/invalid-schema {:schema :catn}
   {:type :malli.core/invalid-schema,
    :message :malli.core/invalid-schema,
    :data {:schema :catn}}

My guess is one of three things is going wrong:

  1. My transformer needs to handle stuff that isn’t :date-time
  2. My custom registry also needs to be merged with the default registry instead of replacing it
  3. Something else :wink:

If someone can help nudge me in the right direction and/or point me to some documentation of how to write a custom transformer, I’d be really grateful!

It turns out that my error was one of the things I explained to myself during the course of writing this, my first ClojureVerse post (though given the out I gave myself with hypothesis 3, I suppose there was no way for me to be wrong per se).

My custom registry replaced the default one, so it had no way to handle anything but :date-time. Merging it with the default schemas instead did the trick:

(def schema-registry
  {:registry (merge (m/default-schemas)
                    {:date-time (m/-simple-schema {:pred t/date-time?
                                                   :type :date-time})})})

(m/decode [:catn [:timestamp :date-time]]
          ["2021-03-29T10:00:00"]
          schema-registry
          date-time-transformer)
;; => [#time/date-time "2021-03-29T10:00"]

(m/decode [:catn [:timestamp :date-time] [:weight :int]]
          ["2021-03-29T10:00:00" "27"]
          schema-registry
          (mt/transformer date-time-transformer mt/string-transformer))
;; => [#time/date-time "2021-03-29T10:00" 27]

I would still love a pointer to any docs explaining how to write custom transformers, though! I can kinda muddle through, but I’d prefer to read something that helps me understand the best way to do things. :slight_smile:

2 Likes