Welcome reitit.middleware

This commit is contained in:
Tommi Reiman 2017-08-15 08:57:37 +03:00
parent 252497fca3
commit 55a97e604b
3 changed files with 142 additions and 137 deletions

View file

@ -0,0 +1,44 @@
(ns reitit.middleware
(:require [reitit.core :as reitit]))
(defprotocol ExpandMiddleware
(expand-middleware [this]))
(extend-protocol ExpandMiddleware
#?(:clj clojure.lang.APersistentVector
:cljs cljs.core.PersistentVector)
(expand-middleware [[f & args]]
(fn [handler]
(apply f handler args)))
#?(:clj clojure.lang.Fn
:cljs function)
(expand-middleware [this] this)
nil
(expand-middleware [_]))
(defn- ensure-handler! [path meta scope]
(when-not (:handler meta)
(throw (ex-info
(str "path \"" path "\" doesn't have a :handler defined"
(if scope (str " for " scope)))
(merge {:path path, :meta meta}
(if scope {:scope scope}))))))
(defn compose-middleware [middleware]
(->> middleware
(keep identity)
(map expand-middleware)
(apply comp identity)))
(defn compile-handler
([route opts]
(compile-handler route opts nil))
([[path {:keys [middleware handler] :as meta}] _ scope]
(ensure-handler! path meta scope)
((compose-middleware middleware) handler)))
(defn router [data]
(reitit/router data {:compile compile-handler}))

View file

@ -1,47 +1,17 @@
(ns reitit.ring (ns reitit.ring
(:require [meta-merge.core :refer [meta-merge]] (:require [meta-merge.core :refer [meta-merge]]
[reitit.middleware :as middleware]
[reitit.core :as reitit])) [reitit.core :as reitit]))
(defprotocol ExpandMiddleware (def http-methods #{:get :head :patch :delete :options :post :put})
(expand-middleware [this])) (defrecord MethodHandlers [get head patch delete options post put])
(extend-protocol ExpandMiddleware (defn- group-keys [meta]
(reduce-kv
#?(:clj clojure.lang.APersistentVector (fn [[top childs] k v]
:cljs cljs.core.PersistentVector) (if (http-methods k)
(expand-middleware [[f & args]] [top (assoc childs k v)]
(fn [handler] [(assoc top k v) childs])) [{} {}] meta))
(apply f handler args)))
#?(:clj clojure.lang.Fn
:cljs function)
(expand-middleware [this] this)
nil
(expand-middleware [_]))
(defn- ensure-handler! [path meta method]
(when-not (:handler meta)
(throw (ex-info
(str "path \"" path "\" doesn't have a :handler defined"
(if method (str " for method " method)))
{:path path, :method method, :meta meta}))))
(defn- compose-middleware [middleware]
(->> middleware
(keep identity)
(map expand-middleware)
(apply comp identity)))
(defn- compile-handler
([route opts]
(compile-handler route opts nil))
([[path {:keys [middleware handler] :as meta}] _ method]
(ensure-handler! path meta method)
((compose-middleware middleware) handler)))
(defn simple-router [data]
(reitit/router data {:compile compile-handler}))
(defn ring-handler [router] (defn ring-handler [router]
(with-meta (with-meta
@ -57,33 +27,24 @@
(defn get-router [handler] (defn get-router [handler]
(some-> handler meta ::router)) (some-> handler meta ::router))
(def http-methods #{:get :head :patch :delete :options :post :put}) (defn coerce-handler [[path meta] {:keys [expand]}]
(defrecord MethodHandlers [get head patch delete options post put])
(defn- group-keys [meta]
(reduce-kv
(fn [[top childs] k v]
(if (http-methods k)
[top (assoc childs k v)]
[(assoc top k v) childs])) [{} {}] meta))
(defn coerce-method-handler [[path meta] {:keys [expand]}]
[path (reduce [path (reduce
(fn [acc method] (fn [acc method]
(if (contains? acc method) (if (contains? acc method)
(update acc method expand) (update acc method expand)
acc)) meta http-methods)]) acc)) meta http-methods)])
(defn compile-method-handler [[path meta] opts] (defn compile-handler [[path meta] opts]
(let [[top childs] (group-keys meta)] (let [[top childs] (group-keys meta)]
(if-not (seq childs) (if-not (seq childs)
(compile-handler [path meta] opts) (middleware/compile-handler [path meta] opts)
(let [handlers (map->MethodHandlers (let [handlers (map->MethodHandlers
(reduce-kv (reduce-kv
#(assoc %1 %2 (compile-handler [path (meta-merge top %3)] opts %2)) #(assoc %1 %2 (middleware/compile-handler
[path (meta-merge top %3)] opts %2))
{} childs)) {} childs))
default-handler (if (:handler top) (compile-handler [path meta] opts)) default-handler (if (:handler top) (middleware/compile-handler [path meta] opts))
resolved-handler (fn [method] (or (method handlers) default-handler))] resolved-handler #(or (% handlers) default-handler)]
(fn (fn
([request] ([request]
(if-let [handler (resolved-handler (:request-method request))] (if-let [handler (resolved-handler (:request-method request))]
@ -92,6 +53,6 @@
(if-let [handler (resolved-handler (:request-method request))] (if-let [handler (resolved-handler (:request-method request))]
(handler request respond raise)))))))) (handler request respond raise))))))))
(defn method-router [data] (defn router [data]
(reitit/router data {:coerce coerce-method-handler (reitit/router data {:coerce coerce-handler
:compile compile-method-handler})) :compile compile-handler}))

View file

@ -1,5 +1,6 @@
(ns reitit.ring-test (ns reitit.ring-test
(:require [clojure.test :refer [deftest testing is]] (:require [clojure.test :refer [deftest testing is]]
[reitit.middleware :as middleware]
[reitit.ring :as ring]) [reitit.ring :as ring])
#?(:clj #?(:clj
(:import (clojure.lang ExceptionInfo)))) (:import (clojure.lang ExceptionInfo))))
@ -23,102 +24,101 @@
([request respond raise] ([request respond raise]
(respond (handler request)))) (respond (handler request))))
(deftest ring-test (deftest middleware-router-test
(testing "simple-router" (testing "all paths should have a handler"
(is (thrown-with-msg?
ExceptionInfo
#"path \"/ping\" doesn't have a :handler defined"
(middleware/router ["/ping"]))))
(testing "all paths should have a handler" (testing "ring-handler"
(is (thrown-with-msg? (let [api-mw #(mw % :api)
ExceptionInfo router (middleware/router
#"path \"/ping\" doesn't have a :handler defined" [["/ping" handler]
(ring/simple-router ["/ping"])))) ["/api" {:middleware [api-mw]}
["/ping" handler]
["/admin" {:middleware [[mw :admin]]}
["/ping" handler]]]])
app (ring/ring-handler router)]
(testing "ring-handler" (testing "router can be extracted"
(let [api-mw #(mw % :api) (is (= router (ring/get-router app))))
router (ring/simple-router
[["/ping" handler]
["/api" {:middleware [api-mw]}
["/ping" handler]
["/admin" {:middleware [[mw :admin]]}
["/ping" handler]]]])
app (ring/ring-handler router)]
(testing "router can be extracted" (testing "not found"
(is (= router (ring/get-router app)))) (is (= nil (app {:uri "/favicon.ico"}))))
(testing "not found" (testing "normal handler"
(is (= nil (app {:uri "/favicon.ico"})))) (is (= {:status 200, :body [:ok]}
(app {:uri "/ping"}))))
(testing "normal handler" (testing "with middleware"
(is (= {:status 200, :body [:ok]} (is (= {:status 200, :body [:api :ok :api]}
(app {:uri "/ping"})))) (app {:uri "/api/ping"}))))
(testing "with middleware" (testing "with nested middleware"
(is (= {:status 200, :body [:api :ok :api]} (is (= {:status 200, :body [:api :admin :ok :admin :api]}
(app {:uri "/api/ping"})))) (app {:uri "/api/admin/ping"}))))
(testing "with nested middleware" (testing "3-arity"
(let [result (atom nil)
respond (partial reset! result), raise ::not-called]
(app {:uri "/api/admin/ping"} respond raise)
(is (= {:status 200, :body [:api :admin :ok :admin :api]} (is (= {:status 200, :body [:api :admin :ok :admin :api]}
(app {:uri "/api/admin/ping"})))) @result)))))))
(testing "3-arity"
(let [result (atom nil)
respond (partial reset! result), raise ::not-called]
(app {:uri "/api/admin/ping"} respond raise)
(is (= {:status 200, :body [:api :admin :ok :admin :api]}
@result)))))))
(testing "method-router" (deftest ring-router-test
(testing "all paths should have a handler" (testing "all paths should have a handler"
(is (thrown-with-msg? (is (thrown-with-msg?
ExceptionInfo ExceptionInfo
#"path \"/ping\" doesn't have a :handler defined for method :get" #"path \"/ping\" doesn't have a :handler defined for :get"
(ring/method-router ["/ping" {:get {}}])))) (ring/router ["/ping" {:get {}}]))))
(testing "ring-handler" (testing "ring-handler"
(let [api-mw #(mw % :api) (let [api-mw #(mw % :api)
router (ring/method-router router (ring/router
[["/api" {:middleware [api-mw]} [["/api" {:middleware [api-mw]}
["/all" handler] ["/all" handler]
["/get" {:get handler}] ["/get" {:get handler}]
["/users" {:middleware [[mw :users]] ["/users" {:middleware [[mw :users]]
:get handler :get handler
:post {:handler handler :post {:handler handler
:middleware [[mw :post]]} :middleware [[mw :post]]}
:handler handler}]]]) :handler handler}]]])
app (ring/ring-handler router)] app (ring/ring-handler router)]
(testing "router can be extracted" (testing "router can be extracted"
(is (= router (ring/get-router app)))) (is (= router (ring/get-router app))))
(testing "not found" (testing "not found"
(is (= nil (app {:uri "/favicon.ico"})))) (is (= nil (app {:uri "/favicon.ico"}))))
(testing "catch all handler" (testing "catch all handler"
(is (= {:status 200, :body [:api :ok :api]} (is (= {:status 200, :body [:api :ok :api]}
(app {:uri "/api/all" :request-method :get})))) (app {:uri "/api/all" :request-method :get}))))
(testing "just get handler" (testing "just get handler"
(is (= {:status 200, :body [:api :ok :api]} (is (= {:status 200, :body [:api :ok :api]}
(app {:uri "/api/get" :request-method :get}))) (app {:uri "/api/get" :request-method :get})))
(is (= nil (app {:uri "/api/get" :request-method :post})))) (is (= nil (app {:uri "/api/get" :request-method :post}))))
(testing "expanded method handler" (testing "expanded method handler"
(is (= {:status 200, :body [:api :users :ok :users :api]} (is (= {:status 200, :body [:api :users :ok :users :api]}
(app {:uri "/api/users" :request-method :get})))) (app {:uri "/api/users" :request-method :get}))))
(testing "method handler with middleware" (testing "method handler with middleware"
(is (= {:status 200, :body [:api :users :post :ok :post :users :api]}
(app {:uri "/api/users" :request-method :post}))))
(testing "fallback handler"
(is (= {:status 200, :body [:api :users :ok :users :api]}
(app {:uri "/api/users" :request-method :put}))))
(testing "3-arity"
(let [result (atom nil)
respond (partial reset! result), raise ::not-called]
(app {:uri "/api/users" :request-method :post} respond raise)
(is (= {:status 200, :body [:api :users :post :ok :post :users :api]} (is (= {:status 200, :body [:api :users :post :ok :post :users :api]}
(app {:uri "/api/users" :request-method :post})))) @result)))))))
(testing "fallback handler"
(is (= {:status 200, :body [:api :users :ok :users :api]}
(app {:uri "/api/users" :request-method :put}))))
(testing "3-arity"
(let [result (atom nil)
respond (partial reset! result), raise ::not-called]
(app {:uri "/api/users" :request-method :post} respond raise)
(is (= {:status 200, :body [:api :users :post :ok :post :users :api]}
@result))))))))