mirror of
https://github.com/metosin/reitit.git
synced 2025-12-18 08:51:12 +00:00
Add :on-invalid for custom handling of invalid values in coercer
This commit is contained in:
parent
7520d20f12
commit
3b5100d8a9
3 changed files with 75 additions and 11 deletions
|
|
@ -20,7 +20,8 @@
|
||||||
(-decode [this value])
|
(-decode [this value])
|
||||||
(-encode [this value])
|
(-encode [this value])
|
||||||
(-validate [this value])
|
(-validate [this value])
|
||||||
(-explain [this value]))
|
(-explain [this value])
|
||||||
|
(-on-invalid [this value explained]))
|
||||||
|
|
||||||
(defprotocol TransformationProvider
|
(defprotocol TransformationProvider
|
||||||
(-transformer [this options]))
|
(-transformer [this options]))
|
||||||
|
|
@ -37,18 +38,24 @@
|
||||||
(def json-transformer-provider (-provider (mt/json-transformer)))
|
(def json-transformer-provider (-provider (mt/json-transformer)))
|
||||||
(def default-transformer-provider (-provider nil))
|
(def default-transformer-provider (-provider nil))
|
||||||
|
|
||||||
(defn- -coercer [schema type transformers f {:keys [validate enabled options]}]
|
(defn- -return-coercion-error
|
||||||
|
[value error]
|
||||||
|
(coercion/map->CoercionError (assoc error :transformed value)))
|
||||||
|
|
||||||
|
(defn- -coercer [schema type transformers f {:keys [validate enabled options on-invalid]}]
|
||||||
(if schema
|
(if schema
|
||||||
(let [->coercer (fn [t]
|
(let [->coercer (fn [t]
|
||||||
(let [decoder (if t (m/decoder schema options t) identity)
|
(let [decoder (if t (m/decoder schema options t) identity)
|
||||||
encoder (if t (m/encoder schema options t) identity)
|
encoder (if t (m/encoder schema options t) identity)
|
||||||
validator (if validate (m/validator schema options) (constantly true))
|
validator (if validate (m/validator schema options) (constantly true))
|
||||||
explainer (m/explainer schema options)]
|
explainer (m/explainer schema options)
|
||||||
|
report (or on-invalid -return-coercion-error)]
|
||||||
(reify Coercer
|
(reify Coercer
|
||||||
(-decode [_ value] (decoder value))
|
(-decode [_ value] (decoder value))
|
||||||
(-encode [_ value] (encoder value))
|
(-encode [_ value] (encoder value))
|
||||||
(-validate [_ value] (validator value))
|
(-validate [_ value] (validator value))
|
||||||
(-explain [_ value] (explainer value)))))
|
(-explain [_ value] (explainer value))
|
||||||
|
(-on-invalid [_ value explained] (report value explained)))))
|
||||||
{:keys [formats default]} (transformers type)
|
{:keys [formats default]} (transformers type)
|
||||||
default-coercer (->coercer default)
|
default-coercer (->coercer default)
|
||||||
format-coercers (some->> (for [[f t] formats] [f (->coercer t)]) (filter second) (seq) (into {}))
|
format-coercers (some->> (for [[f t] formats] [f (->coercer t)]) (filter second) (seq) (into {}))
|
||||||
|
|
@ -63,8 +70,7 @@
|
||||||
(if (-validate coercer transformed)
|
(if (-validate coercer transformed)
|
||||||
transformed
|
transformed
|
||||||
(let [error (-explain coercer transformed)]
|
(let [error (-explain coercer transformed)]
|
||||||
(coercion/map->CoercionError
|
(-on-invalid coercer transformed error))))
|
||||||
(assoc error :transformed transformed)))))
|
|
||||||
value))
|
value))
|
||||||
;; encode: decode -> validate -> encode
|
;; encode: decode -> validate -> encode
|
||||||
(fn [value format]
|
(fn [value format]
|
||||||
|
|
@ -73,8 +79,7 @@
|
||||||
(if (-validate coercer transformed)
|
(if (-validate coercer transformed)
|
||||||
(-encode coercer transformed)
|
(-encode coercer transformed)
|
||||||
(let [error (-explain coercer transformed)]
|
(let [error (-explain coercer transformed)]
|
||||||
(coercion/map->CoercionError
|
(-on-invalid coercer transformed error)))
|
||||||
(assoc error :transformed transformed))))
|
|
||||||
value))))))))
|
value))))))))
|
||||||
|
|
||||||
(defn- -query-string-coercer
|
(defn- -query-string-coercer
|
||||||
|
|
@ -120,6 +125,8 @@
|
||||||
:default-values true
|
:default-values true
|
||||||
;; encode-error
|
;; encode-error
|
||||||
:encode-error nil
|
:encode-error nil
|
||||||
|
;; custom handler for validation errors (vs returning them)
|
||||||
|
:on-invalid nil
|
||||||
;; malli options
|
;; malli options
|
||||||
:options nil})
|
:options nil})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,8 @@
|
||||||
(is (= 500 status))))))))))
|
(is (= 500 status))))))))))
|
||||||
|
|
||||||
(deftest malli-coercion-test
|
(deftest malli-coercion-test
|
||||||
(let [create (fn [interceptors]
|
(let [most-recent (atom nil)
|
||||||
|
create (fn [interceptors]
|
||||||
(http/ring-handler
|
(http/ring-handler
|
||||||
(http/router
|
(http/router
|
||||||
["/api"
|
["/api"
|
||||||
|
|
@ -213,6 +214,16 @@
|
||||||
{:status 200
|
{:status 200
|
||||||
:body (-> req :parameters :body)})}}]
|
:body (-> req :parameters :body)})}}]
|
||||||
|
|
||||||
|
["/warn" {:summary "log and return original"
|
||||||
|
:coercion (reitit.coercion.malli/create {:transformers {},
|
||||||
|
:on-invalid (fn [value error]
|
||||||
|
(reset! most-recent error)
|
||||||
|
value)})
|
||||||
|
:post {:parameters {:body [:map [:x int?]]}
|
||||||
|
:responses {200 {:body [:map [:x int?]]}}
|
||||||
|
:handler (fn [req]
|
||||||
|
{:status 200
|
||||||
|
:body (-> req :parameters :body)})}}]
|
||||||
["/skip" {:summary "skip"
|
["/skip" {:summary "skip"
|
||||||
:coercion (reitit.coercion.malli/create {:enabled false})
|
:coercion (reitit.coercion.malli/create {:enabled false})
|
||||||
:post {:parameters {:body [:map [:x int?]]}
|
:post {:parameters {:body [:map [:x int?]]}
|
||||||
|
|
@ -290,6 +301,19 @@
|
||||||
:reitit.interceptor/handler]
|
:reitit.interceptor/handler]
|
||||||
(mounted-interceptor app "/api/validate" :post))))
|
(mounted-interceptor app "/api/validate" :post))))
|
||||||
|
|
||||||
|
(testing "validation, log on invalid"
|
||||||
|
(is (= 123 (:body (app {:uri "/api/warn"
|
||||||
|
:request-method :post
|
||||||
|
:muuntaja/request {:format "application/edn"}
|
||||||
|
:body-params 123}))))
|
||||||
|
(is (= [:reitit.http.coercion/coerce-exceptions
|
||||||
|
:reitit.http.coercion/coerce-request
|
||||||
|
:reitit.http.coercion/coerce-response
|
||||||
|
:reitit.interceptor/handler]
|
||||||
|
(mounted-interceptor app "/api/warn" :post)))
|
||||||
|
(let [received @most-recent]
|
||||||
|
(is (= 123 (-> @most-recent :errors first :value)))))
|
||||||
|
|
||||||
(testing "no tranformation & validation"
|
(testing "no tranformation & validation"
|
||||||
(is (= 123 (:body (app {:uri "/api/no-op"
|
(is (= 123 (:body (app {:uri "/api/no-op"
|
||||||
:request-method :post
|
:request-method :post
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,8 @@
|
||||||
(reduce custom-meta-merge-checking-parameters left (cons right more))))
|
(reduce custom-meta-merge-checking-parameters left (cons right more))))
|
||||||
|
|
||||||
(deftest malli-coercion-test
|
(deftest malli-coercion-test
|
||||||
(let [create (fn [middleware routes]
|
(let [most-recent (atom nil)
|
||||||
|
create (fn [middleware routes]
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
routes
|
routes
|
||||||
|
|
@ -270,6 +271,17 @@
|
||||||
{:status 200
|
{:status 200
|
||||||
:body (-> req :parameters :body)})}}]
|
:body (-> req :parameters :body)})}}]
|
||||||
|
|
||||||
|
["/warn" {:summary "log and return original"
|
||||||
|
:coercion (reitit.coercion.malli/create {:transformers {},
|
||||||
|
:on-invalid (fn [value error]
|
||||||
|
(reset! most-recent error)
|
||||||
|
value)})
|
||||||
|
:post {:parameters {:body [:map [:x int?]]}
|
||||||
|
:responses {200 {:body [:map [:x int?]]}}
|
||||||
|
:handler (fn [req]
|
||||||
|
{:status 200
|
||||||
|
:body (-> req :parameters :body)})}}]
|
||||||
|
|
||||||
["/skip" {:summary "skip"
|
["/skip" {:summary "skip"
|
||||||
:coercion (reitit.coercion.malli/create {:enabled false})
|
:coercion (reitit.coercion.malli/create {:enabled false})
|
||||||
:post {:parameters {:body [:map [:x int?]]}
|
:post {:parameters {:body [:map [:x int?]]}
|
||||||
|
|
@ -311,7 +323,16 @@
|
||||||
:handler (fn [req]
|
:handler (fn [req]
|
||||||
{:status 200
|
{:status 200
|
||||||
:body (-> req :parameters :body)})}}]
|
:body (-> req :parameters :body)})}}]
|
||||||
|
["/warn" {:summary "log and return original"
|
||||||
|
:coercion (reitit.coercion.malli/create {:transformers {}
|
||||||
|
:on-invalid (fn [value error]
|
||||||
|
(reset! most-recent error)
|
||||||
|
value)})
|
||||||
|
:post {:parameters {:body {:x int?}}
|
||||||
|
:responses {200 {:body {:x int?}}}
|
||||||
|
:handler (fn [req]
|
||||||
|
{:status 200
|
||||||
|
:body (-> req :parameters :body)})}}]
|
||||||
["/skip" {:summary "skip"
|
["/skip" {:summary "skip"
|
||||||
:coercion (reitit.coercion.malli/create {:enabled false})
|
:coercion (reitit.coercion.malli/create {:enabled false})
|
||||||
:post {:parameters {:body {:x int?}}
|
:post {:parameters {:body {:x int?}}
|
||||||
|
|
@ -397,6 +418,18 @@
|
||||||
:reitit.ring.coercion/coerce-response]
|
:reitit.ring.coercion/coerce-response]
|
||||||
(mounted-middleware app "/api/no-op" :post))))
|
(mounted-middleware app "/api/no-op" :post))))
|
||||||
|
|
||||||
|
(testing "validate and log when invalid"
|
||||||
|
(reset! most-recent nil)
|
||||||
|
(is (= 123 (:body (app {:uri "/api/warn"
|
||||||
|
:request-method :post
|
||||||
|
:muuntaja/request {:format "application/edn"}
|
||||||
|
:body-params 123}))))
|
||||||
|
(is (= 123 (-> @most-recent :errors first :value)))
|
||||||
|
(is (= [:reitit.ring.coercion/coerce-exceptions
|
||||||
|
:reitit.ring.coercion/coerce-request
|
||||||
|
:reitit.ring.coercion/coerce-response]
|
||||||
|
(mounted-middleware app "/api/warn" :post))))
|
||||||
|
|
||||||
(testing "skipping coercion"
|
(testing "skipping coercion"
|
||||||
(is (= nil (:body (app {:uri "/api/skip"
|
(is (= nil (:body (app {:uri "/api/skip"
|
||||||
:request-method :post
|
:request-method :post
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue