mirror of
https://github.com/metosin/reitit.git
synced 2025-12-21 18:11:12 +00:00
commit
cc8bd542dd
9 changed files with 394 additions and 160 deletions
|
|
@ -6,8 +6,8 @@ A friendly data-driven router for Clojure(Script).
|
|||
* Route [conflict resolution](https://metosin.github.io/reitit/basics/route_conflicts.html)
|
||||
* First-class [route data](https://metosin.github.io/reitit/basics/route_data.html)
|
||||
* Bi-directional routing
|
||||
* [Ring-router](https://metosin.github.io/reitit/ring/ring.html) with [data-driven middleware](https://metosin.github.io/reitit/ring/data_driven_middleware.html)
|
||||
* [Pluggable coercion](https://metosin.github.io/reitit/coercion/coercion.html) ([schema](https://github.com/plumatic/schema) & [clojure.spec](https://clojure.org/about/spec))
|
||||
* [Ring-router](https://metosin.github.io/reitit/ring/ring.html) with [data-driven middleware](https://metosin.github.io/reitit/ring/data_driven_middleware.html)
|
||||
* Extendable
|
||||
* Modular
|
||||
* [Fast](https://metosin.github.io/reitit/performance.html)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* [Configuring Routers](advanced/configuring_routers.md)
|
||||
* [Different Routers](advanced/different_routers.md)
|
||||
* [Route Validation](advanced/route_validation.md)
|
||||
* [Interceptors](advanced/interceptors.md)
|
||||
* [Ring](ring/README.md)
|
||||
* [Ring-router](ring/ring.md)
|
||||
* [Dynamic Extensions](ring/dynamic_extensions.md)
|
||||
|
|
@ -26,4 +27,3 @@
|
|||
* [Performance](performance.md)
|
||||
* [FAQ](faq.md)
|
||||
* TODO: Swagger & OpenAPI
|
||||
* TODO: Interceptors
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
* [Configuring Routers](configuring_routers.md)
|
||||
* [Different Routers](different_routers.md)
|
||||
* [Route Validation](route_validation.md)
|
||||
* [Interceptors](interceptors.md)
|
||||
|
|
|
|||
11
doc/advanced/interceptors.md
Normal file
11
doc/advanced/interceptors.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Interceptors
|
||||
|
||||
Reitit also supports [Pedestal](pedestal.io)-style [interceptors](http://pedestal.io/reference/interceptors).
|
||||
|
||||
## work in progress
|
||||
|
||||
* port the (coericon) middleware into interceptors
|
||||
* separate Clojure(Script) runner?
|
||||
* Docs
|
||||
* Samples
|
||||
|
||||
|
|
@ -9,22 +9,19 @@
|
|||
(defrecord Interceptor [name enter leave error])
|
||||
(defrecord Endpoint [data interceptors])
|
||||
|
||||
(defn create [{:keys [name wrap compile] :as m}]
|
||||
(when (and wrap compile)
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Interceptor can't have both :wrap and :compile defined " m) m)))
|
||||
(map->Interceptor m))
|
||||
|
||||
(def ^:dynamic *max-compile-depth* 10)
|
||||
|
||||
(extend-protocol IntoInterceptor
|
||||
|
||||
#?(:clj clojure.lang.APersistentVector
|
||||
:cljs cljs.core.PersistentVector)
|
||||
(into-interceptor [[f & args] data opts]
|
||||
(if-let [{:keys [wrap] :as mw} (into-interceptor f data opts)]
|
||||
(assoc mw :wrap #(apply wrap % args))))
|
||||
(into-interceptor [[f & args :as form] data opts]
|
||||
(when (and (seq args) (not (fn? f)))
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Invalid Interceptor form: " form "")
|
||||
{:form form})))
|
||||
(into-interceptor (apply f args) data opts))
|
||||
|
||||
#?(:clj clojure.lang.Fn
|
||||
:cljs function)
|
||||
|
|
@ -35,12 +32,12 @@
|
|||
#?(:clj clojure.lang.PersistentArrayMap
|
||||
:cljs cljs.core.PersistentArrayMap)
|
||||
(into-interceptor [this data opts]
|
||||
(into-interceptor (create this) data opts))
|
||||
(into-interceptor (map->Interceptor this) data opts))
|
||||
|
||||
#?(:clj clojure.lang.PersistentHashMap
|
||||
:cljs cljs.core.PersistentHashMap)
|
||||
(into-interceptor [this data opts]
|
||||
(into-interceptor (create this) data opts))
|
||||
(into-interceptor (map->Interceptor this) data opts))
|
||||
|
||||
Interceptor
|
||||
(into-interceptor [{:keys [compile] :as this} data opts]
|
||||
|
|
@ -70,24 +67,37 @@
|
|||
(merge {:path path, :data data}
|
||||
(if scope {:scope scope}))))))
|
||||
|
||||
(defn expand [interceptors data opts]
|
||||
(defn- expand-and-transform
|
||||
[interceptors data {:keys [::transform] :or {transform identity} :as opts}]
|
||||
(->> interceptors
|
||||
(keep #(into-interceptor % data opts))
|
||||
(transform)
|
||||
(keep #(into-interceptor % data opts))
|
||||
(into [])))
|
||||
|
||||
(defn interceptor-chain [interceptors handler data opts]
|
||||
(expand (conj interceptors handler) data opts))
|
||||
;;
|
||||
;; public api
|
||||
;;
|
||||
|
||||
(defn chain
|
||||
"Creates a Interceptor chain out of sequence of IntoInterceptor
|
||||
and optionally a handler. Optionally takes route data and (Router) opts."
|
||||
([interceptors handler data]
|
||||
(chain interceptors handler data nil))
|
||||
([interceptors handler data opts]
|
||||
(let [interceptor (some-> (into-interceptor handler data opts)
|
||||
(assoc :name (:name data)))]
|
||||
(-> (expand-and-transform interceptors data opts)
|
||||
(cond-> interceptor (conj interceptor))))))
|
||||
|
||||
(defn compile-result
|
||||
([route opts]
|
||||
(compile-result route opts nil))
|
||||
([[path {:keys [interceptors handler] :as data}]
|
||||
{:keys [::transform] :or {transform identity} :as opts} scope]
|
||||
([[path {:keys [interceptors handler] :as data}] opts scope]
|
||||
(ensure-handler! path data scope)
|
||||
(let [interceptors (expand (transform (expand interceptors data opts)) data opts)]
|
||||
(map->Endpoint
|
||||
{:interceptors (interceptor-chain interceptors handler data opts)
|
||||
:data data}))))
|
||||
(map->Endpoint
|
||||
{:interceptors (chain interceptors handler data opts)
|
||||
:data data})))
|
||||
|
||||
(defn router
|
||||
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
|
||||
|
|
@ -96,10 +106,16 @@
|
|||
Example:
|
||||
|
||||
(router
|
||||
[\"/api\" {:interceptors [i/format i/oauth2]}
|
||||
[\"/users\" {:interceptors [i/delete]
|
||||
[\"/api\" {:interceptors [format-body oauth2]}
|
||||
[\"/users\" {:interceptors [delete]
|
||||
:handler get-user}]])
|
||||
|
||||
Options:
|
||||
|
||||
| key | description |
|
||||
| --------------------------------|-------------|
|
||||
| `:reitit.interceptor/transform` | Function of [Interceptor] => [Interceptor] to transform the expanded Interceptors (default: identity).
|
||||
|
||||
See router options from [[reitit.core/router]]."
|
||||
([data]
|
||||
(router data nil))
|
||||
|
|
@ -110,28 +126,7 @@
|
|||
(defn interceptor-handler [router]
|
||||
(with-meta
|
||||
(fn [path]
|
||||
(some->> path
|
||||
(r/match-by-path router)
|
||||
(some->> (r/match-by-path router path)
|
||||
:result
|
||||
:interceptors))
|
||||
{::router router}))
|
||||
|
||||
(comment
|
||||
(defn execute [r {{:keys [uri]} :request :as ctx}]
|
||||
(if-let [interceptors (-> (r/match-by-path r uri)
|
||||
:result
|
||||
:interceptors)]
|
||||
(as-> ctx $
|
||||
(reduce #(%2 %1) $ (keep :enter interceptors))
|
||||
(reduce #(%2 %1) $ (keep :leave interceptors)))))
|
||||
|
||||
(def r
|
||||
(router
|
||||
["/api" {:interceptors [{:name ::add
|
||||
:enter (fn [ctx]
|
||||
(assoc ctx :enter true))
|
||||
:leave (fn [ctx]
|
||||
(assoc ctx :leave true))}]}
|
||||
["/ping" (fn [ctx] (assoc ctx :response "ok"))]]))
|
||||
|
||||
(execute r {:request {:uri "/api/ping"}}))
|
||||
|
|
|
|||
|
|
@ -9,13 +9,6 @@
|
|||
(defrecord Middleware [name wrap])
|
||||
(defrecord Endpoint [data handler middleware])
|
||||
|
||||
(defn create [{:keys [wrap compile] :as m}]
|
||||
(when (and wrap compile)
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Middleware can't have both :wrap and :compile defined " m) m)))
|
||||
(map->Middleware m))
|
||||
|
||||
(def ^:dynamic *max-compile-depth* 10)
|
||||
|
||||
(extend-protocol IntoMiddleware
|
||||
|
|
@ -35,12 +28,12 @@
|
|||
#?(:clj clojure.lang.PersistentArrayMap
|
||||
:cljs cljs.core.PersistentArrayMap)
|
||||
(into-middleware [this data opts]
|
||||
(into-middleware (create this) data opts))
|
||||
(into-middleware (map->Middleware this) data opts))
|
||||
|
||||
#?(:clj clojure.lang.PersistentHashMap
|
||||
:cljs cljs.core.PersistentHashMap)
|
||||
(into-middleware [this data opts]
|
||||
(into-middleware (create this) data opts))
|
||||
(into-middleware (map->Middleware this) data opts))
|
||||
|
||||
Middleware
|
||||
(into-middleware [{:keys [compile] :as this} data opts]
|
||||
|
|
@ -56,7 +49,7 @@
|
|||
(if-let [middeware (into-middleware (compile data opts) data opts)]
|
||||
(map->Middleware
|
||||
(merge
|
||||
(dissoc this :create)
|
||||
(dissoc this :compile)
|
||||
(impl/strip-nils middeware)))))))
|
||||
|
||||
nil
|
||||
|
|
@ -70,21 +63,38 @@
|
|||
(merge {:path path, :data data}
|
||||
(if scope {:scope scope}))))))
|
||||
|
||||
(defn expand [middleware data opts]
|
||||
(defn- expand-and-transform
|
||||
[middleware data {:keys [::transform] :or {transform identity} :as opts}]
|
||||
(->> middleware
|
||||
(keep #(into-middleware % data opts))
|
||||
(into [])
|
||||
(transform)
|
||||
(keep #(into-middleware % data opts))
|
||||
(into [])))
|
||||
|
||||
(defn compile-handler [middleware handler]
|
||||
(defn- compile-handler [middleware handler]
|
||||
((apply comp identity (keep :wrap middleware)) handler))
|
||||
|
||||
;;
|
||||
;; public api
|
||||
;;
|
||||
|
||||
(defn chain
|
||||
"Creates a Ring middleware chain out of sequence of IntoMiddleware
|
||||
and Handler. Optional takes route data and (Router) opts."
|
||||
([middleware handler]
|
||||
(chain middleware handler nil))
|
||||
([middleware handler data]
|
||||
(chain middleware handler data nil))
|
||||
([middleware handler data opts]
|
||||
(compile-handler (expand-and-transform middleware data opts) handler)))
|
||||
|
||||
(defn compile-result
|
||||
([route opts]
|
||||
(compile-result route opts nil))
|
||||
([[path {:keys [middleware handler] :as data}]
|
||||
{:keys [::transform] :or {transform identity} :as opts} scope]
|
||||
([[path {:keys [middleware handler] :as data}] opts scope]
|
||||
(ensure-handler! path data scope)
|
||||
(let [middleware (expand (transform (expand middleware data opts)) data opts)]
|
||||
(let [middleware (expand-and-transform middleware data opts)]
|
||||
(map->Endpoint
|
||||
{:handler (compile-handler middleware handler)
|
||||
:middleware middleware
|
||||
|
|
@ -101,7 +111,13 @@
|
|||
[\"/users\" {:middleware [wrap-delete]
|
||||
:handler get-user}]])
|
||||
|
||||
See router options from [[reitit.core/router]]."
|
||||
Options:
|
||||
|
||||
| key | description |
|
||||
| -------------------------------|-------------|
|
||||
| `:reitit.middleware/transform` | Function of `[Middleware] => [Middleware]` to transform the expanded Middleware (default: identity).
|
||||
|
||||
See other router options from [[reitit.core/router]]."
|
||||
([data]
|
||||
(router data nil))
|
||||
([data opts]
|
||||
|
|
@ -116,11 +132,3 @@
|
|||
:result
|
||||
:handler))
|
||||
{::router router}))
|
||||
|
||||
(defn chain
|
||||
"Creates a vanilla ring middleware chain out of sequence of
|
||||
IntoMiddleware thingies."
|
||||
([middleware handler data]
|
||||
(chain middleware handler data nil))
|
||||
([middleware handler data opts]
|
||||
(compile-handler (expand middleware data opts) handler)))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
(ns reitit.ring.coercion-middleware
|
||||
(:require [reitit.middleware :as middleware]
|
||||
[reitit.coercion :as coercion]
|
||||
(:require [reitit.coercion :as coercion]
|
||||
[reitit.impl :as impl]))
|
||||
|
||||
(defn handle-coercion-exception [e respond raise]
|
||||
|
|
@ -22,53 +21,50 @@
|
|||
"Middleware for pluggable request coercion.
|
||||
Expects a :coercion of type `reitit.coercion/Coercion`
|
||||
and :parameters from route data, otherwise does not mount."
|
||||
(middleware/create
|
||||
{:name ::coerce-parameters
|
||||
:compile (fn [{:keys [coercion parameters]} opts]
|
||||
(if (and coercion parameters)
|
||||
(let [coercers (coercion/request-coercers coercion parameters opts)]
|
||||
(fn [handler]
|
||||
(fn
|
||||
([request]
|
||||
(let [coerced (coercion/coerce-request coercers request)]
|
||||
(handler (impl/fast-assoc request :parameters coerced))))
|
||||
([request respond raise]
|
||||
(let [coerced (coercion/coerce-request coercers request)]
|
||||
(handler (impl/fast-assoc request :parameters coerced) respond raise))))))))}))
|
||||
{:name ::coerce-parameters
|
||||
:compile (fn [{:keys [coercion parameters]} opts]
|
||||
(if (and coercion parameters)
|
||||
(let [coercers (coercion/request-coercers coercion parameters opts)]
|
||||
(fn [handler]
|
||||
(fn
|
||||
([request]
|
||||
(let [coerced (coercion/coerce-request coercers request)]
|
||||
(handler (impl/fast-assoc request :parameters coerced))))
|
||||
([request respond raise]
|
||||
(let [coerced (coercion/coerce-request coercers request)]
|
||||
(handler (impl/fast-assoc request :parameters coerced) respond raise))))))))})
|
||||
|
||||
(def coerce-response-middleware
|
||||
"Middleware for pluggable response coercion.
|
||||
Expects a :coercion of type `reitit.coercion/Coercion`
|
||||
and :responses from route data, otherwise does not mount."
|
||||
(middleware/create
|
||||
{:name ::coerce-response
|
||||
:compile (fn [{:keys [coercion responses]} opts]
|
||||
(if (and coercion responses)
|
||||
(let [coercers (coercion/response-coercers coercion responses opts)]
|
||||
(fn [handler]
|
||||
(fn
|
||||
([request]
|
||||
(coercion/coerce-response coercers request (handler request)))
|
||||
([request respond raise]
|
||||
(handler request #(respond (coercion/coerce-response coercers request %)) raise)))))))}))
|
||||
{:name ::coerce-response
|
||||
:compile (fn [{:keys [coercion responses]} opts]
|
||||
(if (and coercion responses)
|
||||
(let [coercers (coercion/response-coercers coercion responses opts)]
|
||||
(fn [handler]
|
||||
(fn
|
||||
([request]
|
||||
(coercion/coerce-response coercers request (handler request)))
|
||||
([request respond raise]
|
||||
(handler request #(respond (coercion/coerce-response coercers request %)) raise)))))))})
|
||||
|
||||
(def coerce-exceptions-middleware
|
||||
"Middleware for handling coercion exceptions.
|
||||
Expects a :coercion of type `reitit.coercion/Coercion`
|
||||
and :parameters or :responses from route data, otherwise does not mount."
|
||||
(middleware/create
|
||||
{:name ::coerce-exceptions
|
||||
:compile (fn [{:keys [coercion parameters responses]} _]
|
||||
(if (and coercion (or parameters responses))
|
||||
(fn [handler]
|
||||
(fn
|
||||
([request]
|
||||
(try
|
||||
(handler request)
|
||||
(catch #?(:clj Exception :cljs js/Error) e
|
||||
(handle-coercion-exception e identity #(throw %)))))
|
||||
([request respond raise]
|
||||
(try
|
||||
(handler request respond #(handle-coercion-exception % respond raise))
|
||||
(catch #?(:clj Exception :cljs js/Error) e
|
||||
(handle-coercion-exception e respond raise))))))))}))
|
||||
{:name ::coerce-exceptions
|
||||
:compile (fn [{:keys [coercion parameters responses]} _]
|
||||
(if (and coercion (or parameters responses))
|
||||
(fn [handler]
|
||||
(fn
|
||||
([request]
|
||||
(try
|
||||
(handler request)
|
||||
(catch #?(:clj Exception :cljs js/Error) e
|
||||
(handle-coercion-exception e identity #(throw %)))))
|
||||
([request respond raise]
|
||||
(try
|
||||
(handler request respond #(handle-coercion-exception % respond raise))
|
||||
(catch #?(:clj Exception :cljs js/Error) e
|
||||
(handle-coercion-exception e respond raise))))))))})
|
||||
|
|
|
|||
219
test/cljc/reitit/interceptor_test.cljc
Normal file
219
test/cljc/reitit/interceptor_test.cljc
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
(ns reitit.interceptor-test
|
||||
(:require [clojure.test :refer [deftest testing is are]]
|
||||
[reitit.interceptor :as interceptor]
|
||||
[reitit.core :as r])
|
||||
#?(:clj
|
||||
(:import (clojure.lang ExceptionInfo))))
|
||||
|
||||
(defn execute [interceptors ctx]
|
||||
(as-> ctx $
|
||||
(reduce #(%2 %1) $ (keep :enter interceptors))
|
||||
(reduce #(%2 %1) $ (reverse (keep :leave interceptors)))))
|
||||
|
||||
(def ctx [])
|
||||
|
||||
(defn interceptor [value]
|
||||
{:name value
|
||||
:enter #(conj % value)
|
||||
:leave #(conj % value)})
|
||||
|
||||
(defn enter [value]
|
||||
{:name value
|
||||
:enter #(conj % value)})
|
||||
|
||||
(defn handler [ctx]
|
||||
(conj ctx :ok))
|
||||
|
||||
(defn create [interceptors]
|
||||
(let [chain (interceptor/chain
|
||||
interceptors
|
||||
handler :data nil)]
|
||||
(partial execute chain)))
|
||||
|
||||
(deftest expand-interceptor-test
|
||||
|
||||
(testing "interceptor records"
|
||||
|
||||
(testing "interceptor"
|
||||
(let [calls (atom 0)
|
||||
enter (fn [value]
|
||||
(swap! calls inc)
|
||||
(fn [ctx]
|
||||
(conj ctx value)))]
|
||||
|
||||
(testing "as function"
|
||||
(reset! calls 0)
|
||||
(let [app (create [(enter :value)])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :ok] (app ctx)))
|
||||
(is (= 1 @calls)))))
|
||||
|
||||
(testing "as interceptor vector"
|
||||
(reset! calls 0)
|
||||
(let [app (create [[enter :value]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :ok] (app ctx)))
|
||||
(is (= 1 @calls)))))
|
||||
|
||||
(testing "as map"
|
||||
(reset! calls 0)
|
||||
(let [app (create [{:enter (enter :value)}])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :ok] (app ctx)))
|
||||
(is (= 1 @calls)))))
|
||||
|
||||
(testing "as Interceptor"
|
||||
(reset! calls 0)
|
||||
(let [app (create [(interceptor/map->Interceptor {:enter (enter :value)})])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :ok] (app ctx)))
|
||||
(is (= 1 @calls)))))))
|
||||
|
||||
(testing "compiled interceptor"
|
||||
(let [calls (atom 0)
|
||||
i1 (fn [value]
|
||||
{:compile (fn [data _]
|
||||
(swap! calls inc)
|
||||
(fn [ctx]
|
||||
(into ctx [data value])))})
|
||||
i3 (fn [value]
|
||||
{:compile (fn [data _]
|
||||
(swap! calls inc)
|
||||
{:compile (fn [data _]
|
||||
(swap! calls inc)
|
||||
(i1 value))})})]
|
||||
|
||||
(testing "as function"
|
||||
(reset! calls 0)
|
||||
(let [app (create [[i1 :value]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:data :value :ok] (app ctx)))
|
||||
(is (= 2 @calls)))))
|
||||
|
||||
(testing "as interceptor"
|
||||
(reset! calls 0)
|
||||
(let [app (create [(i1 :value)])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:data :value :ok] (app ctx)))
|
||||
(is (= 2 @calls)))))
|
||||
|
||||
(testing "deeply compiled interceptor"
|
||||
(reset! calls 0)
|
||||
(let [app (create [[i3 :value]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:data :value :ok] (app ctx)))
|
||||
(is (= 4 @calls)))))
|
||||
|
||||
(testing "too deeply compiled interceptor fails"
|
||||
(binding [interceptor/*max-compile-depth* 2]
|
||||
(is (thrown?
|
||||
ExceptionInfo
|
||||
#"Too deep Interceptor compilation"
|
||||
(create [[i3 :value]])))))
|
||||
|
||||
(testing "nil unmounts the interceptor"
|
||||
(let [app (create [{:compile (constantly nil)}
|
||||
{:compile (constantly nil)}])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:ok] (app ctx))))))))))
|
||||
|
||||
(defn create-app [router]
|
||||
(let [handler (interceptor/interceptor-handler router)]
|
||||
(fn [path]
|
||||
(when-let [interceptors (handler path)]
|
||||
(execute interceptors [])))))
|
||||
|
||||
(deftest interceptor-handler-test
|
||||
|
||||
(testing "all paths should have a handler"
|
||||
(is (thrown-with-msg?
|
||||
ExceptionInfo
|
||||
#"path \"/ping\" doesn't have a :handler defined"
|
||||
(interceptor/router ["/ping"]))))
|
||||
|
||||
(testing "interceptor-handler"
|
||||
(let [api-interceptor (interceptor :api)
|
||||
router (interceptor/router
|
||||
[["/ping" handler]
|
||||
["/api" {:interceptors [api-interceptor]}
|
||||
["/ping" handler]
|
||||
["/admin" {:interceptors [[interceptor :admin]]}
|
||||
["/ping" handler]]]])
|
||||
app (create-app router)]
|
||||
|
||||
(testing "not found"
|
||||
(is (= nil (app "/favicon.ico"))))
|
||||
|
||||
(testing "normal handler"
|
||||
(is (= [:ok] (app "/ping"))))
|
||||
|
||||
(testing "with interceptor"
|
||||
(is (= [:api :ok :api] (app "/api/ping"))))
|
||||
|
||||
(testing "with nested interceptor"
|
||||
(is (= [:api :admin :ok :admin :api] (app "/api/admin/ping"))))
|
||||
|
||||
(testing ":compile interceptor can be unmounted at creation-time"
|
||||
(let [i1 {:name ::i1, :compile (constantly (interceptor ::i1))}
|
||||
i2 {:name ::i2, :compile (constantly nil)}
|
||||
i3 (interceptor ::i3)
|
||||
router (interceptor/router
|
||||
["/api" {:name ::api
|
||||
:interceptors [i1 i2 i3 i2]
|
||||
:handler handler}])
|
||||
app (create-app router)]
|
||||
|
||||
(is (= [::i1 ::i3 :ok ::i3 ::i1] (app "/api")))
|
||||
|
||||
(testing "routes contain list of actually applied interceptors"
|
||||
(is (= [::i1 ::i3 ::api] (->> (r/routes router)
|
||||
first
|
||||
last
|
||||
:interceptors
|
||||
(map :name)))))
|
||||
|
||||
(testing "match contains list of actually applied interceptors"
|
||||
(is (= [::i1 ::i3 ::api] (->> "/api"
|
||||
(r/match-by-path router)
|
||||
:result
|
||||
:interceptors
|
||||
(map :name))))))))))
|
||||
|
||||
(deftest chain-test
|
||||
(testing "chain can produce interceptor chain of any IntoInterceptor"
|
||||
(let [i1 {:compile (constantly (interceptor ::i1))}
|
||||
i2 {:compile (constantly nil)}
|
||||
i3 (interceptor ::i3)
|
||||
i4 (interceptor ::i4)
|
||||
i5 {:compile (fn [{:keys [mount?]} _]
|
||||
(when mount?
|
||||
(interceptor ::i5)))}
|
||||
chain1 (interceptor/chain [i1 i2 i3 i4 i5] handler {:mount? true})
|
||||
chain2 (interceptor/chain [i1 i2 i3 i4 i5] handler {:mount? false})
|
||||
chain3 (interceptor/chain [i1 i2 i3 i4 i5] nil {:mount? false})]
|
||||
(is (= [::i1 ::i3 ::i4 ::i5 :ok ::i5 ::i4 ::i3 ::i1] (execute chain1 [])))
|
||||
(is (= [::i1 ::i3 ::i4 :ok ::i4 ::i3 ::i1] (execute chain2 [])))
|
||||
(is (= [::i1 ::i3 ::i4 ::i4 ::i3 ::i1] (execute chain3 []))))))
|
||||
|
||||
(deftest interceptor-transform-test
|
||||
(let [debug-i (enter ::debug)
|
||||
create (fn [options]
|
||||
(create-app
|
||||
(interceptor/router
|
||||
["/ping" {:interceptors [(enter ::olipa)
|
||||
(enter ::kerran)
|
||||
(enter ::avaruus)]
|
||||
:handler handler}]
|
||||
options)))]
|
||||
|
||||
(testing "by default, all interceptors are applied in order"
|
||||
(let [app (create nil)]
|
||||
(is (= [::olipa ::kerran ::avaruus :ok] (app "/ping")))))
|
||||
|
||||
(testing "interceptors can be re-ordered"
|
||||
(let [app (create {::interceptor/transform (partial sort-by :name)})]
|
||||
(is (= [::avaruus ::kerran ::olipa :ok] (app "/ping")))))
|
||||
|
||||
(testing "adding debug interceptor between interceptors"
|
||||
(let [app (create {::interceptor/transform #(interleave % (repeat debug-i))})]
|
||||
(is (= [::olipa ::debug ::kerran ::debug ::avaruus ::debug :ok] (app "/ping")))))))
|
||||
|
|
@ -1,75 +1,80 @@
|
|||
(ns reitit.middleware-test
|
||||
(:require [clojure.test :refer [deftest testing is are]]
|
||||
[reitit.middleware :as middleware]
|
||||
[clojure.set :as set]
|
||||
[reitit.core :as r])
|
||||
#?(:clj
|
||||
(:import (clojure.lang ExceptionInfo))))
|
||||
|
||||
(def request [])
|
||||
|
||||
(defn handler [request]
|
||||
(conj request :ok))
|
||||
|
||||
(defn create [middleware]
|
||||
(middleware/chain
|
||||
middleware
|
||||
handler
|
||||
:data
|
||||
nil))
|
||||
|
||||
(deftest expand-middleware-test
|
||||
|
||||
(testing "middleware records"
|
||||
|
||||
(testing ":wrap & :compile are exclusive"
|
||||
(is (thrown-with-msg?
|
||||
ExceptionInfo
|
||||
#"Middleware can't have both :wrap and :compile defined"
|
||||
(middleware/create
|
||||
{:name ::test
|
||||
:wrap identity
|
||||
:compile (constantly identity)}))))
|
||||
|
||||
(testing "middleware"
|
||||
(let [calls (atom 0)
|
||||
wrap (fn [handler value]
|
||||
(swap! calls inc)
|
||||
(fn [request]
|
||||
[value request]))
|
||||
->app (fn [ast handler]
|
||||
(middleware/compile-handler
|
||||
(middleware/expand ast :data {})
|
||||
handler))]
|
||||
(handler (conj request value))))]
|
||||
|
||||
(testing "as middleware function"
|
||||
(testing "as function"
|
||||
(reset! calls 0)
|
||||
(let [app (->app [[#(wrap % :value)]] identity)]
|
||||
(let [app (create [#(wrap % :value)])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :request] (app :request)))
|
||||
(is (= [:value :ok] (app request)))
|
||||
(is (= 1 @calls)))))
|
||||
|
||||
(testing "as middleware vector"
|
||||
(testing "as function vector"
|
||||
(reset! calls 0)
|
||||
(let [app (->app [[wrap :value]] identity)]
|
||||
(let [app (create [[#(wrap % :value)]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :request] (app :request)))
|
||||
(is (= [:value :ok] (app request)))
|
||||
(is (= 1 @calls)))))
|
||||
|
||||
(testing "as function vector with value(s)"
|
||||
(reset! calls 0)
|
||||
(let [app (create [[wrap :value]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :ok] (app request)))
|
||||
(is (= 1 @calls)))))
|
||||
|
||||
(testing "as map"
|
||||
(reset! calls 0)
|
||||
(let [app (->app [[{:wrap #(wrap % :value)}]] identity)]
|
||||
(let [app (create [[{:wrap #(wrap % :value)}]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :request] (app :request)))
|
||||
(is (= [:value :ok] (app request)))
|
||||
(is (= 1 @calls)))))
|
||||
|
||||
(testing "as map vector"
|
||||
(reset! calls 0)
|
||||
(let [app (->app [[{:wrap wrap} :value]] identity)]
|
||||
(let [app (create [[{:wrap wrap} :value]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :request] (app :request)))
|
||||
(is (= [:value :ok] (app request)))
|
||||
(is (= 1 @calls)))))
|
||||
|
||||
(testing "as Middleware"
|
||||
(reset! calls 0)
|
||||
(let [app (->app [[(middleware/create {:wrap #(wrap % :value)})]] identity)]
|
||||
(let [app (create [[(middleware/map->Middleware {:wrap #(wrap % :value)})]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :request] (app :request)))
|
||||
(is (= [:value :ok] (app request)))
|
||||
(is (= 1 @calls)))))
|
||||
|
||||
(testing "as Middleware vector"
|
||||
(reset! calls 0)
|
||||
(let [app (->app [[(middleware/create {:wrap wrap}) :value]] identity)]
|
||||
(let [app (create [[(middleware/map->Middleware {:wrap wrap}) :value]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :request] (app :request)))
|
||||
(is (= [:value :ok] (app request)))
|
||||
(is (= 1 @calls)))))))
|
||||
|
||||
(testing "compiled Middleware"
|
||||
|
|
@ -79,53 +84,52 @@
|
|||
(fn [handler value]
|
||||
(swap! calls inc)
|
||||
(fn [request]
|
||||
[data value request])))}
|
||||
(handler (into request [data value])))))}
|
||||
mw3 {:compile (fn [data _]
|
||||
(swap! calls inc)
|
||||
{:compile (fn [data _]
|
||||
(swap! calls inc)
|
||||
mw)})}
|
||||
->app (fn [ast handler]
|
||||
(middleware/compile-handler
|
||||
(middleware/expand ast :data {})
|
||||
handler))]
|
||||
mw)})}]
|
||||
|
||||
(testing "as map"
|
||||
(reset! calls 0)
|
||||
(let [app (->app [[mw :value]] identity)]
|
||||
(let [app (create [[mw :value]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:data :value :request] (app :request)))
|
||||
(is (= [:data :value :ok] (app request)))
|
||||
(is (= 2 @calls)))))
|
||||
|
||||
(testing "as Middleware"
|
||||
(reset! calls 0)
|
||||
(let [app (->app [[(middleware/create mw) :value]] identity)]
|
||||
(let [app (create [[(middleware/map->Middleware mw) :value]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:data :value :request] (app :request)))
|
||||
(is (= [:data :value :ok] (app request)))
|
||||
(is (= 2 @calls)))))
|
||||
|
||||
(testing "deeply compiled Middleware"
|
||||
(reset! calls 0)
|
||||
(let [app (->app [[(middleware/create mw3) :value]] identity)]
|
||||
(let [app (create [[(middleware/map->Middleware mw3) :value]])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:data :value :request] (app :request)))
|
||||
(is (= [:data :value :ok] (app request)))
|
||||
(is (= 4 @calls)))))
|
||||
|
||||
(testing "too deeply compiled Middleware fails"
|
||||
(binding [middleware/*max-compile-depth* 2]
|
||||
(is (thrown? ExceptionInfo (->app [[(middleware/create mw3) :value]] identity)))))
|
||||
(is (thrown?
|
||||
ExceptionInfo
|
||||
#"Too deep Middleware compilation"
|
||||
(create [[(middleware/map->Middleware mw3) :value]])))))
|
||||
|
||||
(testing "nil unmounts the middleware"
|
||||
(let [app (->app [{:compile (constantly nil)}
|
||||
{:compile (constantly nil)}] identity)]
|
||||
(let [app (create [{:compile (constantly nil)}
|
||||
{:compile (constantly nil)}])]
|
||||
(dotimes [_ 10]
|
||||
(is (= :request (app :request))))))))))
|
||||
(is (= [:ok] (app request))))))))))
|
||||
|
||||
(defn create-app [router]
|
||||
(let [h (middleware/middleware-handler router)]
|
||||
(fn [path]
|
||||
(if-let [f (h path)]
|
||||
(f [])))))
|
||||
(f request)))))
|
||||
|
||||
(deftest middleware-handler-test
|
||||
|
||||
|
|
@ -188,7 +192,7 @@
|
|||
(map :name))))))))))
|
||||
|
||||
(deftest chain-test
|
||||
(testing "chain can produce middlware chain of any IntoMiddleware"
|
||||
(testing "chain can produce middleware chain of any IntoMiddleware"
|
||||
(let [mw (fn [handler value]
|
||||
#(conj (handler (conj % value)) value))
|
||||
handler #(conj % :ok)
|
||||
|
|
|
|||
Loading…
Reference in a new issue