fn -> handler in interceptors

This commit is contained in:
Tommi Reiman 2018-08-13 08:17:55 +03:00
parent 59cbb25688
commit 5e7be28eb7
2 changed files with 70 additions and 76 deletions

View file

@ -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

View file

@ -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"