reitit

gtbono 2025-01-10T03:27:57.502859Z

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

Filipe Silva 2025-01-10T15:35:34.505439Z

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"},


  ,)

Filipe Silva 2025-01-17T11:28:59.425889Z

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"}

  ,)

👍 2
Filipe Silva 2025-01-17T11:29:51.630129Z

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

2025-01-17T18:01:16.766659Z

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.

2025-01-14T15:29:35.869179Z

It looks like you are using the default reitit malli coercion coercion rcm/coercion.

2025-01-14T15:30:46.419029Z

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).

2025-01-14T15:32:00.861889Z

It should work to use a custom :coercion in router-data that has a composite transformer for json that handles the time schemas.

2025-01-14T15:33:54.208039Z

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

2025-01-13T18:51:00.627179Z

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.