mirror of
https://github.com/metosin/reitit.git
synced 2026-01-30 01:30:34 +00:00
fn -> handler in interceptors
This commit is contained in:
parent
59cbb25688
commit
5e7be28eb7
2 changed files with 70 additions and 76 deletions
|
|
@ -7,8 +7,12 @@
|
||||||
(defprotocol IntoInterceptor
|
(defprotocol IntoInterceptor
|
||||||
(into-interceptor [this data opts]))
|
(into-interceptor [this data opts]))
|
||||||
|
|
||||||
(defrecord Interceptor [name enter leave error])
|
(defrecord Interceptor [name handler? enter leave error])
|
||||||
(defrecord Endpoint [data interceptors])
|
(defrecord Endpoint [data interceptors])
|
||||||
|
(defrecord Context [request response exception])
|
||||||
|
|
||||||
|
(defn context [request]
|
||||||
|
(map->Context {:request request}))
|
||||||
|
|
||||||
(def ^:dynamic *max-compile-depth* 10)
|
(def ^:dynamic *max-compile-depth* 10)
|
||||||
|
|
||||||
|
|
@ -44,9 +48,12 @@
|
||||||
|
|
||||||
#?(:clj clojure.lang.Fn
|
#?(:clj clojure.lang.Fn
|
||||||
:cljs function)
|
:cljs function)
|
||||||
(into-interceptor [this _ _]
|
(into-interceptor [this data opts]
|
||||||
(map->Interceptor
|
(into-interceptor
|
||||||
{:enter this}))
|
{:handler? true
|
||||||
|
:enter (fn [ctx]
|
||||||
|
(assoc ctx :response (this (:request ctx))))}
|
||||||
|
data opts))
|
||||||
|
|
||||||
#?(:clj clojure.lang.PersistentArrayMap
|
#?(:clj clojure.lang.PersistentArrayMap
|
||||||
:cljs cljs.core.PersistentArrayMap)
|
:cljs cljs.core.PersistentArrayMap)
|
||||||
|
|
@ -78,43 +85,30 @@
|
||||||
nil
|
nil
|
||||||
(into-interceptor [_ _ _]))
|
(into-interceptor [_ _ _]))
|
||||||
|
|
||||||
(defn- ensure-handler! [path data scope]
|
|
||||||
(when-not (:handler data)
|
|
||||||
(throw (ex-info
|
|
||||||
(str "path \"" path "\" doesn't have a :handler defined"
|
|
||||||
(if scope (str " for " scope)))
|
|
||||||
(merge {:path path, :data data}
|
|
||||||
(if scope {:scope scope}))))))
|
|
||||||
|
|
||||||
(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 [])))
|
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; public api
|
;; public api
|
||||||
;;
|
;;
|
||||||
|
|
||||||
(defn chain
|
(defn chain
|
||||||
"Creates a Interceptor chain out of sequence of IntoInterceptor
|
"Creates a Interceptor chain out of sequence of IntoInterceptor
|
||||||
and optionally a handler. Optionally takes route data and (Router) opts."
|
Optionally takes route data and (Router) opts."
|
||||||
([interceptors handler data]
|
([interceptors]
|
||||||
(chain interceptors handler data nil))
|
(chain interceptors nil nil))
|
||||||
([interceptors handler data opts]
|
([interceptors data]
|
||||||
(let [interceptor (some-> (into-interceptor handler data opts)
|
(chain interceptors data nil))
|
||||||
(assoc :name (:name data)))]
|
([interceptors data {:keys [::transform] :or {transform identity} :as opts}]
|
||||||
(-> (expand-and-transform interceptors data opts)
|
(->> interceptors
|
||||||
(cond-> interceptor (conj interceptor))))))
|
(keep #(into-interceptor % data opts))
|
||||||
|
(transform)
|
||||||
|
(keep #(into-interceptor % data opts))
|
||||||
|
(into []))))
|
||||||
|
|
||||||
(defn compile-result
|
(defn compile-result
|
||||||
([route opts]
|
([route opts]
|
||||||
(compile-result route opts nil))
|
(compile-result route opts nil))
|
||||||
([[_ {:keys [interceptors handler] :as data}] opts _]
|
([[_ {:keys [interceptors handler] :as data}] opts _]
|
||||||
(map->Endpoint
|
(map->Endpoint
|
||||||
{:interceptors (chain interceptors handler data opts)
|
{:interceptors (chain (into (vec interceptors) [handler]) data opts)
|
||||||
:data data})))
|
:data data})))
|
||||||
|
|
||||||
(defn router
|
(defn router
|
||||||
|
|
|
||||||
|
|
@ -5,32 +5,39 @@
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(:import (clojure.lang ExceptionInfo))))
|
(:import (clojure.lang ExceptionInfo))))
|
||||||
|
|
||||||
|
(def ctx (interceptor/context []))
|
||||||
|
|
||||||
(defn execute [interceptors ctx]
|
(defn execute [interceptors ctx]
|
||||||
(as-> ctx $
|
(as-> ctx $
|
||||||
(reduce #(%2 %1) $ (keep :enter interceptors))
|
(reduce #(%2 %1) $ (keep :enter interceptors))
|
||||||
(reduce #(%2 %1) $ (reverse (keep :leave interceptors)))))
|
(reduce #(%2 %1) $ (reverse (keep :leave interceptors)))
|
||||||
|
(:response $)))
|
||||||
|
|
||||||
(def ctx [])
|
(defn f [value ctx]
|
||||||
|
(update ctx :request conj value))
|
||||||
|
|
||||||
|
(defn kws [k qk]
|
||||||
|
(keyword (namespace qk) (str (name k) "_" (name qk))))
|
||||||
|
|
||||||
(defn interceptor [value]
|
(defn interceptor [value]
|
||||||
{:name value
|
{:name value
|
||||||
:enter #(conj % value)
|
:enter #(update % :request (fnil conj []) (kws :enter value))
|
||||||
:leave #(conj % value)})
|
:leave #(update % :response (fnil conj []) (kws :leave value))})
|
||||||
|
|
||||||
(defn enter [value]
|
(defn enter [value]
|
||||||
{:name value
|
{:name value
|
||||||
:enter #(conj % value)})
|
:enter (partial f value)})
|
||||||
|
|
||||||
(defn handler [ctx]
|
(defn handler [request]
|
||||||
(conj ctx :ok))
|
(conj request :ok))
|
||||||
|
|
||||||
(defn create
|
(defn create
|
||||||
([interceptors]
|
([interceptors]
|
||||||
(create interceptors nil))
|
(create interceptors nil))
|
||||||
([interceptors opts]
|
([interceptors opts]
|
||||||
(let [chain (interceptor/chain
|
(let [chain (interceptor/chain
|
||||||
interceptors
|
(conj interceptors handler)
|
||||||
handler :data opts)]
|
:data opts)]
|
||||||
(partial execute chain))))
|
(partial execute chain))))
|
||||||
|
|
||||||
(deftest expand-interceptor-test
|
(deftest expand-interceptor-test
|
||||||
|
|
@ -41,8 +48,8 @@
|
||||||
(let [calls (atom 0)
|
(let [calls (atom 0)
|
||||||
enter (fn [value]
|
enter (fn [value]
|
||||||
(swap! calls inc)
|
(swap! calls inc)
|
||||||
(fn [ctx]
|
{:enter (fn [ctx]
|
||||||
(conj ctx value)))]
|
(update ctx :request conj value))})]
|
||||||
|
|
||||||
(testing "as function"
|
(testing "as function"
|
||||||
(reset! calls 0)
|
(reset! calls 0)
|
||||||
|
|
@ -73,14 +80,14 @@
|
||||||
|
|
||||||
(testing "as map"
|
(testing "as map"
|
||||||
(reset! calls 0)
|
(reset! calls 0)
|
||||||
(let [app (create [{:enter (enter :value)}])]
|
(let [app (create [{:enter (:enter (enter :value))}])]
|
||||||
(dotimes [_ 10]
|
(dotimes [_ 10]
|
||||||
(is (= [:value :ok] (app ctx)))
|
(is (= [:value :ok] (app ctx)))
|
||||||
(is (= 1 @calls)))))
|
(is (= 1 @calls)))))
|
||||||
|
|
||||||
(testing "as Interceptor"
|
(testing "as Interceptor"
|
||||||
(reset! calls 0)
|
(reset! calls 0)
|
||||||
(let [app (create [(interceptor/map->Interceptor {:enter (enter :value)})])]
|
(let [app (create [(interceptor/map->Interceptor {:enter (:enter (enter :value))})])]
|
||||||
(dotimes [_ 10]
|
(dotimes [_ 10]
|
||||||
(is (= [:value :ok] (app ctx)))
|
(is (= [:value :ok] (app ctx)))
|
||||||
(is (= 1 @calls)))))))
|
(is (= 1 @calls)))))))
|
||||||
|
|
@ -90,12 +97,12 @@
|
||||||
i1 (fn [value]
|
i1 (fn [value]
|
||||||
{:compile (fn [data _]
|
{:compile (fn [data _]
|
||||||
(swap! calls inc)
|
(swap! calls inc)
|
||||||
(fn [ctx]
|
{:enter (fn [ctx]
|
||||||
(into ctx [data value])))})
|
(update ctx :request into [data value]))})})
|
||||||
i3 (fn [value]
|
i3 (fn [value]
|
||||||
{:compile (fn [data _]
|
{:compile (fn [_ _]
|
||||||
(swap! calls inc)
|
(swap! calls inc)
|
||||||
{:compile (fn [data _]
|
{:compile (fn [_ _]
|
||||||
(swap! calls inc)
|
(swap! calls inc)
|
||||||
(i1 value))})})]
|
(i1 value))})})]
|
||||||
|
|
||||||
|
|
@ -137,16 +144,10 @@
|
||||||
(let [handler (interceptor/interceptor-handler router)]
|
(let [handler (interceptor/interceptor-handler router)]
|
||||||
(fn [path]
|
(fn [path]
|
||||||
(when-let [interceptors (handler path)]
|
(when-let [interceptors (handler path)]
|
||||||
(execute interceptors [])))))
|
(execute interceptors ctx)))))
|
||||||
|
|
||||||
(deftest interceptor-handler-test
|
(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"
|
(testing "interceptor-handler"
|
||||||
(let [api-interceptor (interceptor :api)
|
(let [api-interceptor (interceptor :api)
|
||||||
router (interceptor/router
|
router (interceptor/router
|
||||||
|
|
@ -164,36 +165,35 @@
|
||||||
(is (= [:ok] (app "/ping"))))
|
(is (= [:ok] (app "/ping"))))
|
||||||
|
|
||||||
(testing "with interceptor"
|
(testing "with interceptor"
|
||||||
(is (= [:api :ok :api] (app "/api/ping"))))
|
(is (= [:enter_api :ok :leave_api] (app "/api/ping"))))
|
||||||
|
|
||||||
(testing "with nested interceptor"
|
(testing "with nested interceptor"
|
||||||
(is (= [:api :admin :ok :admin :api] (app "/api/admin/ping"))))
|
(is (= [:enter_api :enter_admin :ok :leave_admin :leave_api] (app "/api/admin/ping"))))
|
||||||
|
|
||||||
(testing ":compile interceptor can be unmounted at creation-time"
|
(testing ":compile interceptor can be unmounted at creation-time"
|
||||||
(let [i1 {:name ::i1, :compile (constantly (interceptor ::i1))}
|
(let [i1 {:name ::i1, :compile (constantly (interceptor ::i1))}
|
||||||
i2 {:name ::i2, :compile (constantly nil)}
|
i2 {:name ::i2, :compile (constantly nil)}
|
||||||
i3 (interceptor ::i3)
|
i3 (interceptor ::i3)
|
||||||
router (interceptor/router
|
router (interceptor/router
|
||||||
["/api" {:name ::api
|
["/api" {:interceptors [i1 i2 i3 i2]
|
||||||
:interceptors [i1 i2 i3 i2]
|
|
||||||
:handler handler}])
|
:handler handler}])
|
||||||
app (create-app router)]
|
app (create-app router)]
|
||||||
|
|
||||||
(is (= [::i1 ::i3 :ok ::i3 ::i1] (app "/api")))
|
(is (= [::enter_i1 ::enter_i3 :ok ::leave_i3 ::leave_i1] (app "/api")))
|
||||||
|
|
||||||
(testing "routes contain list of actually applied interceptors"
|
(testing "routes contain list of actually applied interceptors"
|
||||||
(is (= [::i1 ::i3 ::api] (->> (r/compiled-routes router)
|
(is (= [::i1 ::i3 nil] (->> (r/compiled-routes router)
|
||||||
first
|
first
|
||||||
last
|
last
|
||||||
:interceptors
|
:interceptors
|
||||||
(map :name)))))
|
(map :name)))))
|
||||||
|
|
||||||
(testing "match contains list of actually applied interceptors"
|
(testing "match contains list of actually applied interceptors"
|
||||||
(is (= [::i1 ::i3 ::api] (->> "/api"
|
(is (= [::i1 ::i3 nil] (->> "/api"
|
||||||
(r/match-by-path router)
|
(r/match-by-path router)
|
||||||
:result
|
:result
|
||||||
:interceptors
|
:interceptors
|
||||||
(map :name))))))))))
|
(map :name))))))))))
|
||||||
|
|
||||||
(deftest chain-test
|
(deftest chain-test
|
||||||
(testing "chain can produce interceptor chain of any IntoInterceptor"
|
(testing "chain can produce interceptor chain of any IntoInterceptor"
|
||||||
|
|
@ -204,12 +204,12 @@
|
||||||
i5 {:compile (fn [{:keys [mount?]} _]
|
i5 {:compile (fn [{:keys [mount?]} _]
|
||||||
(when mount?
|
(when mount?
|
||||||
(interceptor ::i5)))}
|
(interceptor ::i5)))}
|
||||||
chain1 (interceptor/chain [i1 i2 i3 i4 i5] handler {:mount? true})
|
chain1 (interceptor/chain [i1 i2 i3 i4 i5 handler] {:mount? true})
|
||||||
chain2 (interceptor/chain [i1 i2 i3 i4 i5] handler {:mount? false})
|
chain2 (interceptor/chain [i1 i2 i3 i4 i5 handler] {:mount? false})
|
||||||
chain3 (interceptor/chain [i1 i2 i3 i4 i5] nil {:mount? false})]
|
chain3 (interceptor/chain [i1 i2 i3 i4 i5] {:mount? false})]
|
||||||
(is (= [::i1 ::i3 ::i4 ::i5 :ok ::i5 ::i4 ::i3 ::i1] (execute chain1 [])))
|
(is (= [::enter_i1 ::enter_i3 ::enter_i4 ::enter_i5 :ok ::leave_i5 ::leave_i4 ::leave_i3 ::leave_i1] (execute chain1 ctx)))
|
||||||
(is (= [::i1 ::i3 ::i4 :ok ::i4 ::i3 ::i1] (execute chain2 [])))
|
(is (= [::enter_i1 ::enter_i3 ::enter_i4 :ok ::leave_i4 ::leave_i3 ::leave_i1] (execute chain2 ctx)))
|
||||||
(is (= [::i1 ::i3 ::i4 ::i4 ::i3 ::i1] (execute chain3 []))))))
|
(is (= [::leave_i4 ::leave_i3 ::leave_i1] (execute chain3 ctx))))))
|
||||||
|
|
||||||
(deftest interceptor-transform-test
|
(deftest interceptor-transform-test
|
||||||
(let [debug-i (enter ::debug)
|
(let [debug-i (enter ::debug)
|
||||||
|
|
@ -227,7 +227,7 @@
|
||||||
(is (= [::olipa ::kerran ::avaruus :ok] (app "/ping")))))
|
(is (= [::olipa ::kerran ::avaruus :ok] (app "/ping")))))
|
||||||
|
|
||||||
(testing "interceptors can be re-ordered"
|
(testing "interceptors can be re-ordered"
|
||||||
(let [app (create {::interceptor/transform (partial sort-by :name)})]
|
(let [app (create {::interceptor/transform (partial sort-by (juxt :handler? :name))})]
|
||||||
(is (= [::avaruus ::kerran ::olipa :ok] (app "/ping")))))
|
(is (= [::avaruus ::kerran ::olipa :ok] (app "/ping")))))
|
||||||
|
|
||||||
(testing "adding debug interceptor between interceptors"
|
(testing "adding debug interceptor between interceptors"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue