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)