diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 813bbc20..e0216610 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -20,7 +20,8 @@ (-decode [this value]) (-encode [this value]) (-validate [this value]) - (-explain [this value])) + (-explain [this value]) + (-on-invalid [this value explained])) (defprotocol TransformationProvider (-transformer [this options])) @@ -37,18 +38,24 @@ (def json-transformer-provider (-provider (mt/json-transformer))) (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 (let [->coercer (fn [t] (let [decoder (if t (m/decoder schema options t) identity) encoder (if t (m/encoder schema options t) identity) 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 (-decode [_ value] (decoder value)) (-encode [_ value] (encoder 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) default-coercer (->coercer default) format-coercers (some->> (for [[f t] formats] [f (->coercer t)]) (filter second) (seq) (into {})) @@ -63,8 +70,7 @@ (if (-validate coercer transformed) transformed (let [error (-explain coercer transformed)] - (coercion/map->CoercionError - (assoc error :transformed transformed))))) + (-on-invalid coercer transformed error)))) value)) ;; encode: decode -> validate -> encode (fn [value format] @@ -73,8 +79,7 @@ (if (-validate coercer transformed) (-encode coercer transformed) (let [error (-explain coercer transformed)] - (coercion/map->CoercionError - (assoc error :transformed transformed)))) + (-on-invalid coercer transformed error))) value)))))))) (defn- -query-string-coercer @@ -120,6 +125,8 @@ :default-values true ;; encode-error :encode-error nil + ;; custom handler for validation errors (vs returning them) + :on-invalid nil ;; malli options :options nil}) diff --git a/test/clj/reitit/http_coercion_test.clj b/test/clj/reitit/http_coercion_test.clj index 53c749a7..0d460e38 100644 --- a/test/clj/reitit/http_coercion_test.clj +++ b/test/clj/reitit/http_coercion_test.clj @@ -192,7 +192,8 @@ (is (= 500 status)))))))))) (deftest malli-coercion-test - (let [create (fn [interceptors] + (let [most-recent (atom nil) + create (fn [interceptors] (http/ring-handler (http/router ["/api" @@ -213,6 +214,16 @@ {:status 200 :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" :coercion (reitit.coercion.malli/create {:enabled false}) :post {:parameters {:body [:map [:x int?]]} @@ -290,6 +301,19 @@ :reitit.interceptor/handler] (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" (is (= 123 (:body (app {:uri "/api/no-op" :request-method :post diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index aae8d69a..0b57b7e7 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -245,7 +245,8 @@ (reduce custom-meta-merge-checking-parameters left (cons right more)))) (deftest malli-coercion-test - (let [create (fn [middleware routes] + (let [most-recent (atom nil) + create (fn [middleware routes] (ring/ring-handler (ring/router routes @@ -270,6 +271,17 @@ {:status 200 :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" :coercion (reitit.coercion.malli/create {:enabled false}) :post {:parameters {:body [:map [:x int?]]} @@ -311,7 +323,16 @@ :handler (fn [req] {:status 200 :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" :coercion (reitit.coercion.malli/create {:enabled false}) :post {:parameters {:body {:x int?}} @@ -397,6 +418,18 @@ :reitit.ring.coercion/coerce-response] (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" (is (= nil (:body (app {:uri "/api/skip" :request-method :post