drop create- from interceptors

This commit is contained in:
Tommi Reiman 2018-09-02 17:29:51 +03:00
parent 89fa82b366
commit 8cd06c2bfb
4 changed files with 285 additions and 81 deletions

View file

@ -89,14 +89,16 @@
context))})) context))}))
(defn ring-handler (defn ring-handler
"Creates a ring-handler out of a http-router, "Creates a ring-handler out of a http-router, optional default ring-handler
a default ring-handler and options map, with the following keys: and options map, with the following keys:
| key | description | | key | description |
| ----------------|-------------| | ----------------|-------------|
| `:executor` | `reitit.interceptor.Executor` for the interceptor chain | `:executor` | `reitit.interceptor.Executor` for the interceptor chain
| `:interceptors` | Optional sequence of interceptors that are always run before any other interceptors, even for the default handler" | `:interceptors` | Optional sequence of interceptors that are always run before any other interceptors, even for the default handler"
[router default-handler {:keys [executor interceptors]}] ([router opts]
(ring-handler router nil opts))
([router default-handler {:keys [executor interceptors]}]
(let [default-handler (or default-handler (fn ([_]) ([_ respond _] (respond nil)))) (let [default-handler (or default-handler (fn ([_]) ([_ respond _] (respond nil))))
default-queue (->> [default-handler] default-queue (->> [default-handler]
(concat interceptors) (concat interceptors)
@ -104,6 +106,7 @@
(interceptor/queue executor)) (interceptor/queue executor))
router-opts (-> (r/options router) router-opts (-> (r/options router)
(assoc ::interceptor/queue (partial interceptor/queue executor)) (assoc ::interceptor/queue (partial interceptor/queue executor))
(dissoc :data) ; data is already merged into routes
(cond-> (seq interceptors) (cond-> (seq interceptors)
(update-in [:data :interceptors] (partial into (vec interceptors))))) (update-in [:data :interceptors] (partial into (vec interceptors)))))
router (reitit.http/router (r/routes router) router-opts)] router (reitit.http/router (r/routes router) router-opts)]
@ -142,7 +145,7 @@
(default request))) (default request)))
(default request))) (default request)))
nil)) nil))
{::r/router router}))) {::r/router router}))))
(defn get-router [handler] (defn get-router [handler]
(-> handler meta ::r/router)) (-> handler meta ::r/router))

View file

@ -3,40 +3,43 @@
[reitit.spec :as rs] [reitit.spec :as rs]
[reitit.impl :as impl])) [reitit.impl :as impl]))
(def coerce-request-interceptor (defn coerce-request-interceptor
"Interceptor for pluggable request coercion. "Interceptor for pluggable request coercion.
Expects a :coercion of type `reitit.coercion/Coercion` Expects a :coercion of type `reitit.coercion/Coercion`
and :parameters from route data, otherwise does not mount." and :parameters from route data, otherwise does not mount."
[]
{:name ::coerce-request {:name ::coerce-request
:spec ::rs/parameters :spec ::rs/parameters
:compile (fn [{:keys [coercion parameters]} opts] :compile (fn [{:keys [coercion parameters]} opts]
(if (and coercion parameters) (if (and coercion parameters)
(let [coercers (coercion/request-coercers coercion parameters opts)] (let [coercers (coercion/request-coercers coercion parameters opts)]
{:enter {:enter (fn [ctx]
(fn [ctx]
(let [request (:request ctx) (let [request (:request ctx)
coerced (coercion/coerce-request coercers request) coerced (coercion/coerce-request coercers request)
request (impl/fast-assoc request :parameters coerced)] request (impl/fast-assoc request :parameters coerced)]
(assoc ctx :request request)))})))}) (assoc ctx :request request)))})))})
(def coerce-response-interceptor (defn coerce-response-interceptor
"Interceptor for pluggable response coercion. "Interceptor for pluggable response coercion.
Expects a :coercion of type `reitit.coercion/Coercion` Expects a :coercion of type `reitit.coercion/Coercion`
and :responses from route data, otherwise does not mount." and :responses from route data, otherwise does not mount."
[]
{:name ::coerce-response {:name ::coerce-response
:spec ::rs/responses :spec ::rs/responses
:compile (fn [{:keys [coercion responses]} opts] :compile (fn [{:keys [coercion responses]} opts]
(if (and coercion responses) (if (and coercion responses)
(let [coercers (coercion/response-coercers coercion responses opts)] (let [coercers (coercion/response-coercers coercion responses opts)]
{:leave {:leave (fn [ctx]
(fn [ctx] (let [request (:request ctx)
(let [response (coercion/coerce-response coercers (:request ctx) (:response ctx))] response (:response ctx)]
(assoc ctx :response response)))})))}) (let [response (coercion/coerce-response coercers request response)]
(assoc ctx :response response))))})))})
(def coerce-exceptions-interceptor (defn coerce-exceptions-interceptor
"Interceptor for handling coercion exceptions. "Interceptor for handling coercion exceptions.
Expects a :coercion of type `reitit.coercion/Coercion` Expects a :coercion of type `reitit.coercion/Coercion`
and :parameters or :responses from route data, otherwise does not mount." and :parameters or :responses from route data, otherwise does not mount."
[]
{:name ::coerce-exceptions {:name ::coerce-exceptions
:compile (fn [{:keys [coercion parameters responses]} _] :compile (fn [{:keys [coercion parameters responses]} _]
(if (and coercion (or parameters responses)) (if (and coercion (or parameters responses))

View file

@ -25,7 +25,7 @@
[metosin/spec-tools "0.7.1"] [metosin/spec-tools "0.7.1"]
[metosin/schema-tools "0.10.3"] [metosin/schema-tools "0.10.3"]
[metosin/ring-swagger-ui "2.2.10"] [metosin/ring-swagger-ui "2.2.10"]
[metosin/muuntaja "0.6.0-alpha4"] [metosin/muuntaja "0.6.0-alpha5"]
[metosin/jsonista "0.2.1"] [metosin/jsonista "0.2.1"]
[metosin/sieppari "0.0.0-alpha4"]] [metosin/sieppari "0.0.0-alpha4"]]
@ -62,7 +62,7 @@
[ring "1.6.3"] [ring "1.6.3"]
[ikitommi/immutant-web "3.0.0-alpha1"] [ikitommi/immutant-web "3.0.0-alpha1"]
[metosin/muuntaja "0.6.0-alpha4"] [metosin/muuntaja "0.6.0-alpha5"]
[metosin/ring-swagger-ui "2.2.10"] [metosin/ring-swagger-ui "2.2.10"]
[metosin/sieppari "0.0.0-alpha4"] [metosin/sieppari "0.0.0-alpha4"]
[metosin/jsonista "0.2.1"] [metosin/jsonista "0.2.1"]

View file

@ -0,0 +1,198 @@
(ns reitit.http-coercion-test
(:require [clojure.test :refer [deftest testing is]]
[schema.core :as s]
[reitit.http :as http]
[reitit.http.coercion :as rrc]
[reitit.coercion.spec :as spec]
[reitit.coercion.schema :as schema]
[muuntaja.interceptor]
[jsonista.core :as j]
[reitit.interceptor.sieppari :as sieppari])
(:import (clojure.lang ExceptionInfo)
(java.io ByteArrayInputStream)))
(defn handler [{{{:keys [a]} :query
{:keys [b]} :body
{:keys [c]} :form
{:keys [d]} :header
{:keys [e]} :path} :parameters}]
(if (= 666 a)
{:status 500
:body {:evil true}}
{:status 200
:body {:total (+ a b c d e)}}))
(def valid-request
{:uri "/api/plus/5"
:request-method :get
:query-params {"a" "1"}
:body-params {:b 2}
:form-params {:c 3}
:headers {"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}
:headers {"d" "-40"}})
(deftest spec-coercion-test
(let [create (fn [interceptors]
(http/ring-handler
(http/router
["/api"
["/plus/:e"
{:get {:parameters {:query {:a int?}
:body {:b int?}
:form {:c int?}
:header {:d int?}
:path {:e int?}}
:responses {200 {:body {:total pos-int?}}
500 {:description "fail"}}
:handler handler}}]]
{:data {:interceptors interceptors
:coercion spec/coercion}})
{:executor sieppari/executor}))]
(testing "without exception handling"
(let [app (create [(rrc/coerce-request-interceptor)
(rrc/coerce-response-interceptor)])]
(testing "all good"
(is (= {:status 200
:body {:total 15}}
(app valid-request)))
(is (= {:status 500
:body {:evil true}}
(app (assoc-in valid-request [:query-params "a"] "666")))))
(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 [(rrc/coerce-exceptions-interceptor)
(rrc/coerce-request-interceptor)
(rrc/coerce-response-interceptor)])]
(testing "all good"
(is (= {:status 200
:body {:total 15}}
(app valid-request))))
(testing "invalid request"
(let [{:keys [status]} (app invalid-request)]
(is (= 400 status))))
(testing "invalid response"
(let [{:keys [status]} (app invalid-request2)]
(is (= 500 status))))))))
(deftest schema-coercion-test
(let [create (fn [middleware]
(http/ring-handler
(http/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 {:body {:total (s/constrained s/Int pos? 'positive)}}
500 {:description "fail"}}
:handler handler}}]]
{:data {:interceptors middleware
:coercion schema/coercion}})
{:executor sieppari/executor}))]
(testing "withut exception handling"
(let [app (create [(rrc/coerce-request-interceptor)
(rrc/coerce-response-interceptor)])]
(testing "all good"
(is (= {:status 200
:body {:total 15}}
(app valid-request)))
(is (= {:status 500
:body {:evil true}}
(app (assoc-in valid-request [:query-params "a"] "666")))))
(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 [(rrc/coerce-exceptions-interceptor)
(rrc/coerce-request-interceptor)
(rrc/coerce-response-interceptor)])]
(testing "all good"
(is (= {:status 200
:body {:total 15}}
(app valid-request))))
(testing "invalid request"
(let [{:keys [status]} (app invalid-request)]
(is (= 400 status))))
(testing "invalid response"
(let [{:keys [status]} (app invalid-request2)]
(is (= 500 status))))))))))
(deftest muuntaja-test
(let [app (http/ring-handler
(http/router
["/api"
["/plus"
{:post {:parameters {:body {:int int?, :keyword keyword?}}
:responses {200 {:body {:int int?, :keyword keyword?}}}
:handler (fn [{{:keys [body]} :parameters}]
{:status 200
:body body})}}]]
{:data {:interceptors [(muuntaja.interceptor/format-interceptor)
(rrc/coerce-response-interceptor)
(rrc/coerce-request-interceptor)]
:coercion spec/coercion}})
{:executor sieppari/executor})
request (fn [content-type body]
(-> {:request-method :post
:headers {"content-type" content-type, "accept" content-type}
:uri "/api/plus"
:body body}))
data-edn {:int 1 :keyword :kikka}
data-json {:int 1 :keyword "kikka"}]
(testing "json coercion"
(let [e2e #(-> (request "application/json" (ByteArrayInputStream. (j/write-value-as-bytes %)))
(app) :body (slurp) (j/read-value (j/object-mapper {:decode-key-fn true})))]
(is (= data-json (e2e data-edn)))
(is (= data-json (e2e data-json)))))
(testing "edn coercion"
(let [e2e #(-> (request "application/edn" (pr-str %))
(app) :body slurp (read-string))]
(is (= data-edn (e2e data-edn)))
(is (thrown? ExceptionInfo (e2e data-json)))))))