Implement coercion error handling

This commit is contained in:
Tommi Reiman 2017-11-26 21:51:43 +02:00
parent 4d772c62e1
commit 03d4e8c4bf
2 changed files with 176 additions and 55 deletions

View file

@ -81,9 +81,11 @@
(response-coercion-failed! result coercion value request response) (response-coercion-failed! result coercion value request response)
result)))))) result))))))
;; (defn encode-error [data]
;; middleware (-> data
;; (dissoc :request :response)
(update :coercion protocol/get-name)
(->> (protocol/encode-error (:coercion data)))))
(defn- coerce-request [coercers request] (defn- coerce-request [coercers request]
(reduce-kv (reduce-kv
@ -133,6 +135,20 @@
(let [coercers (request-coercers coercion parameters) (let [coercers (request-coercers coercion parameters)
coerced (coerce-parameters coercers request)] coerced (coerce-parameters coercers request)]
(handler (impl/fast-assoc request :parameters coerced) respond raise))))))) (handler (impl/fast-assoc request :parameters coerced) respond raise)))))))
(defn handle-coercion-exception [e respond raise]
(let [data (ex-data e)]
(if-let [status (condp = (:type data)
::request-coercion 400
::response-coercion 500
nil)]
(respond
{:status status
:body (encode-error data)})
(raise e))))
;;
;; middleware
;;
(def gen-wrap-coerce-parameters (def gen-wrap-coerce-parameters
"Middleware for pluggable request coercion. "Middleware for pluggable request coercion.
@ -195,3 +211,24 @@
(coerce-response coercers request (handler request))) (coerce-response coercers request (handler request)))
([request respond raise] ([request respond raise]
(handler request #(respond (coerce-response coercers request %)) raise)))))))})) (handler request #(respond (coerce-response coercers request %)) raise)))))))}))
(def gen-wrap-coerce-exceptions
"Middleare for coercion exception handling.
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
and :parameters or :responses from route data, otherwise does not mount."
(middleware/create
{:name ::coerce-exceptions
:gen-wrap (fn [{:keys [coercion parameters responses]} _]
(if (and coercion (or parameters responses))
(fn [handler]
(fn
([request]
(try
(handler request)
(catch Exception e
(handle-coercion-exception e identity #(throw %)))))
([request respond raise]
(try
(handler request respond (fn [e] (handle-coercion-exception e respond raise)))
(catch Throwable e
(handle-coercion-exception e respond raise))))))))}))

View file

@ -2,18 +2,43 @@
(:require [clojure.test :refer [deftest testing is]] (:require [clojure.test :refer [deftest testing is]]
[reitit.ring :as ring] [reitit.ring :as ring]
[reitit.ring.coercion :as coercion] [reitit.ring.coercion :as coercion]
[reitit.ring.coercion.spec :as spec]) [reitit.ring.coercion.spec :as spec]
[schema.core :as s]
[reitit.ring.coercion.schema :as schema])
#?(:clj #?(:clj
(:import (clojure.lang ExceptionInfo)))) (:import (clojure.lang ExceptionInfo))))
(defn handler (defn handler [{{{:keys [a]} :query
([{:keys [::mw]}] {:keys [b]} :body
{:status 200 :body (conj mw :ok)}) {:keys [c]} :form
([request respond raise] {:keys [d]} :header
(respond (handler request)))) {:keys [e]} :path} :parameters}]
{:status 200
:body {:total (+ a b c d e)}})
(deftest coercion-test (def valid-request
(let [app (ring/ring-handler {:uri "/api/plus/5"
:request-method :get
:query-params {"a" "1"}
:body-params {:b 2}
:form-params {:c 3}
:header-params {:d 4}})
(def invalid-request
{:uri "/api/plus/5"
:request-method :get})
(def invalid-request2
{:uri "/api/plus/5"
:request-method :get
:query-params {"a" "1"}
:body-params {:b 2}
:form-params {:c 3}
:header-params {:d -40}})
(deftest spec-coercion-test
(let [create (fn [middleware]
(ring/ring-handler
(ring/router (ring/router
["/api" ["/api"
["/plus/:e" ["/plus/:e"
@ -23,41 +48,100 @@
:header {:d int?} :header {:d int?}
:path {:e int?}} :path {:e int?}}
:responses {200 {:schema {:total pos-int?}}} :responses {200 {:schema {:total pos-int?}}}
:handler (fn [{{{:keys [a]} :query :handler handler}}]]
{:keys [b]} :body {:data {:middleware middleware
{:keys [c]} :form :coercion spec/coercion}})))]
{:keys [d]} :header
{:keys [e]} :path} :parameters}] (testing "withut exception handling"
{:status 200 (let [app (create [coercion/gen-wrap-coerce-parameters
:body {:total (+ a b c d e)}})}}]] coercion/gen-wrap-coerce-response])]
{:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response]
:coercion spec/coercion}}))]
(testing "all good" (testing "all good"
(is (= {:status 200 (is (= {:status 200
:body {:total 15}} :body {:total 15}}
(app {:uri "/api/plus/5" (app valid-request))))
:request-method :get
:query-params {"a" "1"}
:body-params {:b 2}
:form-params {:c 3}
:header-params {:d 4}}))))
(testing "invalid request" (testing "invalid request"
(is (thrown-with-msg? (is (thrown-with-msg?
ExceptionInfo ExceptionInfo
#"Request coercion failed" #"Request coercion failed"
(app {:uri "/api/plus/5" (app invalid-request))))
:request-method :get}))))
(testing "invalid response" (testing "invalid response"
(is (thrown-with-msg? (is (thrown-with-msg?
ExceptionInfo ExceptionInfo
#"Response coercion failed" #"Response coercion failed"
(app {:uri "/api/plus/5" (app invalid-request2))))))
:request-method :get
:query-params {"a" "1"} (testing "with exception handling"
:body-params {:b 2} (let [app (create [coercion/gen-wrap-coerce-exceptions
:form-params {:c 3} coercion/gen-wrap-coerce-parameters
:header-params {:d -40}})))))) coercion/gen-wrap-coerce-response])]
(testing "all good"
(is (= {:status 200
:body {:total 15}}
(app valid-request))))
(testing "invalid request"
(let [{:keys [status body]} (app invalid-request)]
(is (= 400 status))))
(testing "invalid response"
(let [{:keys [status body]} (app invalid-request2)]
(is (= 500 status))))))))
(deftest schema-coercion-test
(let [create (fn [middleware]
(ring/ring-handler
(ring/router
["/api"
["/plus/:e"
{:get {:parameters {:query {:a s/Int}
:body {:b s/Int}
:form {:c s/Int}
:header {:d s/Int}
:path {:e s/Int}}
:responses {200 {:schema {:total (s/constrained s/Int pos? 'positive)}}}
:handler handler}}]]
{:data {:middleware middleware
:coercion schema/coercion}})))]
(testing "withut exception handling"
(let [app (create [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response])]
(testing "all good"
(is (= {:status 200
:body {:total 15}}
(app valid-request))))
(testing "invalid request"
(is (thrown-with-msg?
ExceptionInfo
#"Request coercion failed"
(app invalid-request))))
(testing "invalid response"
(is (thrown-with-msg?
ExceptionInfo
#"Response coercion failed"
(app invalid-request2))))
(testing "with exception handling"
(let [app (create [coercion/gen-wrap-coerce-exceptions
coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response])]
(testing "all good"
(is (= {:status 200
:body {:total 15}}
(app valid-request))))
(testing "invalid request"
(let [{:keys [status body]} (app invalid-request)]
(is (= 400 status))))
(testing "invalid response"
(let [{:keys [status body]} (app invalid-request2)]
(is (= 500 status))))))))))