From 927d4d4389480b8e464bb9cc0c80d6a65a3ccd6d Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Tue, 12 Dec 2017 22:27:00 +0200 Subject: [PATCH 1/3] Remove middleware/create & polish stuff --- .../reitit-core/src/reitit/middleware.cljc | 56 ++++++----- .../src/reitit/ring/coercion_middleware.cljc | 80 +++++++-------- test/cljc/reitit/middleware_test.cljc | 98 ++++++++++--------- 3 files changed, 121 insertions(+), 113 deletions(-) diff --git a/modules/reitit-core/src/reitit/middleware.cljc b/modules/reitit-core/src/reitit/middleware.cljc index 57d2536e..d135843b 100644 --- a/modules/reitit-core/src/reitit/middleware.cljc +++ b/modules/reitit-core/src/reitit/middleware.cljc @@ -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))) diff --git a/modules/reitit-ring/src/reitit/ring/coercion_middleware.cljc b/modules/reitit-ring/src/reitit/ring/coercion_middleware.cljc index e2b2cf27..19ea395d 100644 --- a/modules/reitit-ring/src/reitit/ring/coercion_middleware.cljc +++ b/modules/reitit-ring/src/reitit/ring/coercion_middleware.cljc @@ -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))))))))}) diff --git a/test/cljc/reitit/middleware_test.cljc b/test/cljc/reitit/middleware_test.cljc index 3ad16298..39253be8 100644 --- a/test/cljc/reitit/middleware_test.cljc +++ b/test/cljc/reitit/middleware_test.cljc @@ -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) From fd6a39aefcd2a9d13ee4edc210e6b1db3d1880be Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Tue, 12 Dec 2017 22:27:50 +0200 Subject: [PATCH 2/3] Implement Interceptors like Middlewar --- .../reitit-core/src/reitit/interceptor.cljc | 85 ++++--- test/cljc/reitit/interceptor_test.cljc | 219 ++++++++++++++++++ 2 files changed, 259 insertions(+), 45 deletions(-) create mode 100644 test/cljc/reitit/interceptor_test.cljc diff --git a/modules/reitit-core/src/reitit/interceptor.cljc b/modules/reitit-core/src/reitit/interceptor.cljc index 1a030455..6f129621 100644 --- a/modules/reitit-core/src/reitit/interceptor.cljc +++ b/modules/reitit-core/src/reitit/interceptor.cljc @@ -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"}})) diff --git a/test/cljc/reitit/interceptor_test.cljc b/test/cljc/reitit/interceptor_test.cljc new file mode 100644 index 00000000..bb571c8c --- /dev/null +++ b/test/cljc/reitit/interceptor_test.cljc @@ -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"))))))) From 6cfcda9713f703781328eedc05fad862f37fecca Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Tue, 12 Dec 2017 22:40:42 +0200 Subject: [PATCH 3/3] docs --- README.md | 2 +- doc/SUMMARY.md | 2 +- doc/advanced/README.md | 1 + doc/advanced/interceptors.md | 11 +++++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 doc/advanced/interceptors.md diff --git a/README.md b/README.md index c5a84b15..37ae93a9 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/doc/SUMMARY.md b/doc/SUMMARY.md index bf23d439..4ca4b31c 100644 --- a/doc/SUMMARY.md +++ b/doc/SUMMARY.md @@ -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 diff --git a/doc/advanced/README.md b/doc/advanced/README.md index cad41444..3bb47889 100644 --- a/doc/advanced/README.md +++ b/doc/advanced/README.md @@ -3,3 +3,4 @@ * [Configuring Routers](configuring_routers.md) * [Different Routers](different_routers.md) * [Route Validation](route_validation.md) +* [Interceptors](interceptors.md) diff --git a/doc/advanced/interceptors.md b/doc/advanced/interceptors.md new file mode 100644 index 00000000..441fed49 --- /dev/null +++ b/doc/advanced/interceptors.md @@ -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 +