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)
result))))))
;;
;; middleware
;;
(defn encode-error [data]
(-> data
(dissoc :request :response)
(update :coercion protocol/get-name)
(->> (protocol/encode-error (:coercion data)))))
(defn- coerce-request [coercers request]
(reduce-kv
@ -133,6 +135,20 @@
(let [coercers (request-coercers coercion parameters)
coerced (coerce-parameters coercers request)]
(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
"Middleware for pluggable request coercion.
@ -195,3 +211,24 @@
(coerce-response coercers request (handler request)))
([request respond 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,62 +2,146 @@
(:require [clojure.test :refer [deftest testing is]]
[reitit.ring :as ring]
[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
(:import (clojure.lang ExceptionInfo))))
(defn handler
([{:keys [::mw]}]
{:status 200 :body (conj mw :ok)})
([request respond raise]
(respond (handler request))))
(defn handler [{{{:keys [a]} :query
{:keys [b]} :body
{:keys [c]} :form
{:keys [d]} :header
{:keys [e]} :path} :parameters}]
{:status 200
:body {:total (+ a b c d e)}})
(deftest coercion-test
(let [app (ring/ring-handler
(ring/router
["/api"
["/plus/:e"
{:get {:parameters {:query {:a int?}
:body {:b int?}
:form {:c int?}
:header {:d int?}
:path {:e int?}}
:responses {200 {:schema {:total pos-int?}}}
:handler (fn [{{{:keys [a]} :query
{:keys [b]} :body
{:keys [c]} :form
{:keys [d]} :header
{:keys [e]} :path} :parameters}]
{:status 200
:body {:total (+ a b c d e)}})}}]]
{:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response]
:coercion spec/coercion}}))]
(def valid-request
{:uri "/api/plus/5"
:request-method :get
:query-params {"a" "1"}
:body-params {:b 2}
:form-params {:c 3}
:header-params {:d 4}})
(testing "all good"
(is (= {:status 200
:body {:total 15}}
(app {: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})
(testing "invalid request"
(is (thrown-with-msg?
ExceptionInfo
#"Request coercion failed"
(app {: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}})
(testing "invalid response"
(is (thrown-with-msg?
ExceptionInfo
#"Response coercion failed"
(app {: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
["/api"
["/plus/:e"
{:get {:parameters {:query {:a int?}
:body {:b int?}
:form {:c int?}
:header {:d int?}
:path {:e int?}}
:responses {200 {:schema {:total pos-int?}}}
:handler handler}}]]
{:data {:middleware middleware
:coercion spec/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))))))))
(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))))))))))