mirror of
https://github.com/metosin/reitit.git
synced 2025-12-27 20:08:25 +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
|
||||
(into-interceptor [this data opts]))
|
||||
|
||||
(defrecord Interceptor [name enter leave error])
|
||||
(defrecord Interceptor [name handler? enter leave error])
|
||||
(defrecord Endpoint [data interceptors])
|
||||
(defrecord Context [request response exception])
|
||||
|
||||
(defn context [request]
|
||||
(map->Context {:request request}))
|
||||
|
||||
(def ^:dynamic *max-compile-depth* 10)
|
||||
|
||||
|
|
@ -44,9 +48,12 @@
|
|||
|
||||
#?(:clj clojure.lang.Fn
|
||||
:cljs function)
|
||||
(into-interceptor [this _ _]
|
||||
(map->Interceptor
|
||||
{:enter this}))
|
||||
(into-interceptor [this data opts]
|
||||
(into-interceptor
|
||||
{:handler? true
|
||||
:enter (fn [ctx]
|
||||
(assoc ctx :response (this (:request ctx))))}
|
||||
data opts))
|
||||
|
||||
#?(:clj clojure.lang.PersistentArrayMap
|
||||
:cljs cljs.core.PersistentArrayMap)
|
||||
|
|
@ -78,43 +85,30 @@
|
|||
nil
|
||||
(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
|
||||
;;
|
||||
|
||||
(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))))))
|
||||
Optionally takes route data and (Router) opts."
|
||||
([interceptors]
|
||||
(chain interceptors nil nil))
|
||||
([interceptors data]
|
||||
(chain interceptors data nil))
|
||||
([interceptors data {:keys [::transform] :or {transform identity} :as opts}]
|
||||
(->> interceptors
|
||||
(keep #(into-interceptor % data opts))
|
||||
(transform)
|
||||
(keep #(into-interceptor % data opts))
|
||||
(into []))))
|
||||
|
||||
(defn compile-result
|
||||
([route opts]
|
||||
(compile-result route opts nil))
|
||||
([[_ {:keys [interceptors handler] :as data}] opts _]
|
||||
(map->Endpoint
|
||||
{:interceptors (chain interceptors handler data opts)
|
||||
{:interceptors (chain (into (vec interceptors) [handler]) data opts)
|
||||
:data data})))
|
||||
|
||||
(defn router
|
||||
|
|
|
|||
|
|
@ -5,32 +5,39 @@
|
|||
#?(:clj
|
||||
(:import (clojure.lang ExceptionInfo))))
|
||||
|
||||
(def ctx (interceptor/context []))
|
||||
|
||||
(defn execute [interceptors ctx]
|
||||
(as-> ctx $
|
||||
(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]
|
||||
{:name value
|
||||
:enter #(conj % value)
|
||||
:leave #(conj % value)})
|
||||
:enter #(update % :request (fnil conj []) (kws :enter value))
|
||||
:leave #(update % :response (fnil conj []) (kws :leave value))})
|
||||
|
||||
(defn enter [value]
|
||||
{:name value
|
||||
:enter #(conj % value)})
|
||||
:enter (partial f value)})
|
||||
|
||||
(defn handler [ctx]
|
||||
(conj ctx :ok))
|
||||
(defn handler [request]
|
||||
(conj request :ok))
|
||||
|
||||
(defn create
|
||||
([interceptors]
|
||||
(create interceptors nil))
|
||||
(create interceptors nil))
|
||||
([interceptors opts]
|
||||
(let [chain (interceptor/chain
|
||||
interceptors
|
||||
handler :data opts)]
|
||||
(conj interceptors handler)
|
||||
:data opts)]
|
||||
(partial execute chain))))
|
||||
|
||||
(deftest expand-interceptor-test
|
||||
|
|
@ -41,8 +48,8 @@
|
|||
(let [calls (atom 0)
|
||||
enter (fn [value]
|
||||
(swap! calls inc)
|
||||
(fn [ctx]
|
||||
(conj ctx value)))]
|
||||
{:enter (fn [ctx]
|
||||
(update ctx :request conj value))})]
|
||||
|
||||
(testing "as function"
|
||||
(reset! calls 0)
|
||||
|
|
@ -73,14 +80,14 @@
|
|||
|
||||
(testing "as map"
|
||||
(reset! calls 0)
|
||||
(let [app (create [{:enter (enter :value)}])]
|
||||
(let [app (create [{:enter (: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)})])]
|
||||
(let [app (create [(interceptor/map->Interceptor {:enter (:enter (enter :value))})])]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:value :ok] (app ctx)))
|
||||
(is (= 1 @calls)))))))
|
||||
|
|
@ -90,12 +97,12 @@
|
|||
i1 (fn [value]
|
||||
{:compile (fn [data _]
|
||||
(swap! calls inc)
|
||||
(fn [ctx]
|
||||
(into ctx [data value])))})
|
||||
{:enter (fn [ctx]
|
||||
(update ctx :request into [data value]))})})
|
||||
i3 (fn [value]
|
||||
{:compile (fn [data _]
|
||||
{:compile (fn [_ _]
|
||||
(swap! calls inc)
|
||||
{:compile (fn [data _]
|
||||
{:compile (fn [_ _]
|
||||
(swap! calls inc)
|
||||
(i1 value))})})]
|
||||
|
||||
|
|
@ -137,16 +144,10 @@
|
|||
(let [handler (interceptor/interceptor-handler router)]
|
||||
(fn [path]
|
||||
(when-let [interceptors (handler path)]
|
||||
(execute interceptors [])))))
|
||||
(execute interceptors ctx)))))
|
||||
|
||||
(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
|
||||
|
|
@ -164,36 +165,35 @@
|
|||
(is (= [:ok] (app "/ping"))))
|
||||
|
||||
(testing "with interceptor"
|
||||
(is (= [:api :ok :api] (app "/api/ping"))))
|
||||
(is (= [:enter_api :ok :leave_api] (app "/api/ping"))))
|
||||
|
||||
(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"
|
||||
(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]
|
||||
["/api" {:interceptors [i1 i2 i3 i2]
|
||||
:handler handler}])
|
||||
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"
|
||||
(is (= [::i1 ::i3 ::api] (->> (r/compiled-routes router)
|
||||
first
|
||||
last
|
||||
:interceptors
|
||||
(map :name)))))
|
||||
(is (= [::i1 ::i3 nil] (->> (r/compiled-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))))))))))
|
||||
(is (= [::i1 ::i3 nil] (->> "/api"
|
||||
(r/match-by-path router)
|
||||
:result
|
||||
:interceptors
|
||||
(map :name))))))))))
|
||||
|
||||
(deftest chain-test
|
||||
(testing "chain can produce interceptor chain of any IntoInterceptor"
|
||||
|
|
@ -204,12 +204,12 @@
|
|||
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 []))))))
|
||||
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] {:mount? false})]
|
||||
(is (= [::enter_i1 ::enter_i3 ::enter_i4 ::enter_i5 :ok ::leave_i5 ::leave_i4 ::leave_i3 ::leave_i1] (execute chain1 ctx)))
|
||||
(is (= [::enter_i1 ::enter_i3 ::enter_i4 :ok ::leave_i4 ::leave_i3 ::leave_i1] (execute chain2 ctx)))
|
||||
(is (= [::leave_i4 ::leave_i3 ::leave_i1] (execute chain3 ctx))))))
|
||||
|
||||
(deftest interceptor-transform-test
|
||||
(let [debug-i (enter ::debug)
|
||||
|
|
@ -227,7 +227,7 @@
|
|||
(is (= [::olipa ::kerran ::avaruus :ok] (app "/ping")))))
|
||||
|
||||
(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")))))
|
||||
|
||||
(testing "adding debug interceptor between interceptors"
|
||||
|
|
|
|||
Loading…
Reference in a new issue