metosin / reitit

A fast data-driven routing library for Clojure/Script

Home Page:https://cljdoc.org/d/metosin/reitit/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Coercion isn't applied to :body-params

agigao opened this issue · comments

Recently, I've come across a rather confusing coercion behaviour - under :body-params coercion just throws validation error and under :form-params it does the coercion:

(def app
  (ring/ring-handler
    (ring/router
      [
       ["/" {:name :root
             :get  (fn [_] {:status 200 :body "pong"})
             :post {:coercion   rcm/coercion
                    :parameters {:body [:map [:id uuid?]]}
                    :handler    (fn [{:keys [parameters]}]
                                  {:status 200
                                   :body   (-> parameters :form)})}}]]
      {:data {:middleware [rrc/coerce-exceptions-middleware
                           rrc/coerce-request-middleware
                           rrc/coerce-response-middleware]}})))


(app
  {:request-method :post
   :uri            "/"
   :body-params    {:id "644c1b76-4c3c-4067-8aeb-86236bcb5538"}})

;; Result
{:status 400,
 :body {:schema "[:map {:closed true} [:id uuid?]]",
        :errors ({:path [:id],
                  :in [:id],
                  :schema "uuid?",
                  :value "644c1b76-4c3c-4067-8aeb-86236bcb5538",
                  :message "should be a uuid"}),
        :value {:id "644c1b76-4c3c-4067-8aeb-86236bcb5538"},
        :type :reitit.coercion/request-coercion,
        :coercion :malli,
        :in [:request :body-params],
        :humanized {:id ["should be a uuid"]}}}

If I use :form-params instead, it works:

(def app
  (ring/ring-handler
    (ring/router
      [
       ["/" {:name :root
             :get  (fn [_] {:status 200 :body "pong"})
             :post {:coercion   rcm/coercion
                    :parameters {:form [:map [:id uuid?]]}
                    :handler    (fn [{:keys [parameters]}]
                                  {:status 200
                                   :body   (-> parameters :form)})}}]]
      {:data {:middleware [rrc/coerce-exceptions-middleware
                           rrc/coerce-request-middleware
                           rrc/coerce-response-middleware]}})))

(app
  {:request-method :post
   :uri            "/"
   :form-params    {:id "644c1b76-4c3c-4067-8aeb-86236bcb5538"}})

;; Result
{:status 200, :body {:id #uuid"644c1b76-4c3c-4067-8aeb-86236bcb5538"}}

We found out that it's related to the default-options's default transformer that provided under body.

(reitit.coercion.malli/create
   (-> reitit.coercion.malli/default-options
       (assoc-in [:transformers :body :default]
                 reitit.coercion.malli/string-transformer-provider)))

This s-expression can help coerce things in :body.

If it's not the case, please provide information. I might be proven wrong.

It depends on what is your input content-type value.

UUID decoder is included in JSON transformer (and thus is also inherited by string transformer): https://github.com/metosin/malli/blob/master/src/malli/transform.cljc#L223

But if your request content-type is something else, e.g., Transit, no transformer is enabled, as the format might be able to represent the UUID type natively (as Transit can).

If you want to test JSON coercion, set content-type on your request, and encode your request body. (Note that JSON decoding also requires Muuntaja middleware.)

If you want to write test cases and you are going to use Transit with the API, I'd just use UUID types in the test code.

Nice answer @Deraen. Please reopen if you still have a problem!