folks, cross-posting here because it seems more like a reitit question than a ring one, can someone help me figure it out? https://clojurians.slack.com/archives/C0A5GSC6T/p1736474945790409
Heya, is there a way to add the malli :time/instant transformer to malli coercion? I'm trying to get a time round-trip working but having a lot of trouble:
(ns user)
(comment
(add-lib 'metosin/reitit)
(require '[malli.core :as m]
'[malli.registry :as mr]
'[malli.experimental.time :as met]
'[malli.experimental.time.transform :as time.transform]
'[muuntaja.core :as muu]
'[reitit.ring :as ring]
'[reitit.coercion.malli :as rcm]
'[reitit.ring.coercion :as rrc]
'[reitit.ring.middleware.muuntaja :as rrmm]
'[reitit.ring.middleware.parameters :as parameters]
'[jsonista.core :as j])
(def default-registry
(mr/set-default-registry!
(mr/composite-registry
(m/default-schemas)
(met/schemas))))
(def time-map-schema [:map [:t :time/instant]])
(def t "2025-01-10T13:40:28.461494000-00:00")
;; coercion works
(m/coerce time-map-schema {:t t} time.transform/time-transformer)
;; => {:t #object[java.time.Instant 0x6c89d95c "2025-01-10T13:40:28.461494Z"]}
(def router-data
{:data {:coercion rcm/coercion
:muuntaja muu/instance
:middleware [parameters/parameters-middleware
rrc/coerce-request-middleware
rrmm/format-response-middleware
rrc/coerce-response-middleware]}})
(def app (ring/ring-handler
(ring/router
["/time-roundtrip"
{:post {:request {:body time-map-schema}
:responses {200 {:body time-map-schema}}
:handler (fn [r] {:status 200, :body {:t (-> r :body-params :t)}})}}]
router-data)))
(-> {:request-method :post, :uri "/time-roundtrip", :body-params {:t t}}
app :body slurp j/read-value)
;; Request coercion failed
;; {:schema [:map {:closed true} [:t :time/instant]],
;; :value {:t "2025-01-10T13:40:28.461494000-00:00"},
;; :errors
;; ({:path [:t 0],
;; :in [:t],
;; :schema :time/instant,
;; :value "2025-01-10T13:40:28.461494000-00:00"}),
;; :transformed {:t "2025-01-10T13:40:28.461494000-00:00"},
,)
thanks a bunch for the help! after fiddling a lot with it I finally got a coerced round trip working:
(ns user)
(comment
(add-lib 'metosin/reitit)
(require '[malli.core :as m]
'[malli.registry :as mr]
'[malli.experimental.time :as met]
'[malli.experimental.time.transform :as time.transform]
'[malli.transform :as mt]
'[muuntaja.core :as muu]
'[reitit.ring :as ring]
'[reitit.coercion.malli :as rcm]
'[reitit.ring.coercion :as rrc]
'[reitit.ring.middleware.muuntaja :as rrmm]
'[reitit.ring.middleware.parameters :as parameters]
'[jsonista.core :as j])
(def default-registry
(mr/set-default-registry!
(mr/composite-registry
(m/default-schemas)
(met/schemas))))
(def time-map-schema [:map [:t :time/instant]])
(def t "2025-01-10T13:40:28.461494000-00:00")
(def time-map {:t t})
;; coercion works
(m/coerce time-map-schema time-map time.transform/time-transformer)
;; => {:t #object[java.time.Instant 0x6c89d95c "2025-01-10T13:40:28.461494Z"]}
(def json-transformer-provider
"Custom json transformer that supports time transforms."
(#'rcm/-provider (mt/transformer
time.transform/time-transformer
mt/json-transformer)))
(def coercion
(rcm/create (-> rcm/default-options
(assoc-in [:transformers :body :formats "application/json"] json-transformer-provider)
(assoc-in [:transformers :response :formats "application/json"] json-transformer-provider))))
(def router-data
{:data {:coercion coercion
:muuntaja muu/instance
:middleware [parameters/parameters-middleware
rrmm/format-middleware
rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]}})
(def app (ring/ring-handler
(ring/router
["/time-roundtrip"
{:post {:parameters {:body time-map-schema}
:responses {200 {:body time-map-schema}}
:handler (fn [r]
(println ":parameters :body" (-> r :parameters :body))
(println ":body-params" (-> r :body-params))
{:status 200, :body {:t (-> r :parameters :body :t)}})}}]
router-data)))
(-> {:request-method :post
:uri "/time-roundtrip"
:headers {"content-type" "application/json"
"accept" "application/json"}
:body (java.io.ByteArrayInputStream. (j/write-value-as-bytes time-map))}
app :body slurp j/read-value)
;; => {"t" "2025-01-10T13:40:28.461494Z"}
,)found it a bit weird that :body-params doesn't contain the coerced values but :parameters :body does, but I guess that's a problem for another day
Cool!. I think the :body-params may be the raw ones and then any coerced ones get nested under :parameters. I think it works the same for path parameters.
It looks like you are using the default reitit malli coercion coercion rcm/coercion.
The default one use the built in json transformers which don't support the time schemas (even though you added them to your global registry).
It should work to use a custom :coercion in router-data that has a composite transformer for json that handles the time schemas.
For example we have this at work.
(def json-transformer
"Custom json transformer that supports time transforms."
(mt/transformer
mt/strip-extra-keys-transformer
mt/json-transformer
mt/default-value-transformer))
(def transformers
"Customize the default transformers to use our custom json-transformer."
(-> coercion.malli/default-options
:transformers
(assoc-in [:body :formats "application/json"] json-transformer)
(assoc-in [:response :formats "application/json"] json-transformer)))
(defn router-opts []
{:data {:coercion (coercion.malli/create
{;; Use custom transformers.
:transformers transformers Take a look at defining a custom coercion here: https://cljdoc.org/d/metosin/reitit/0.7.2/doc/coercion/malli#configuring-coercion
That describes how to replace the default :coercion rcm/coercion in your router-data with a custom one.