mirror of
https://github.com/metosin/reitit.git
synced 2025-12-18 08:51:12 +00:00
parent
18f8bdfbec
commit
4e22fd2f53
8 changed files with 197 additions and 88 deletions
12
README.md
12
README.md
|
|
@ -114,7 +114,7 @@ Route names:
|
|||
; #Match{:template "/api/user/:id"
|
||||
; :meta {:name :user/user}
|
||||
; :path "/api/user/1"
|
||||
; :handler nil
|
||||
; :result nil
|
||||
; :params {:id "1"}}
|
||||
```
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ Route names:
|
|||
(reitit/match-by-name router ::user)
|
||||
; #PartialMatch{:template "/api/user/:id",
|
||||
; :meta {:name :user/user},
|
||||
; :handler nil,
|
||||
; :result nil,
|
||||
; :params nil,
|
||||
; :required #{:id}}
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ Only a partial match. Let's provide the path-parameters:
|
|||
; #Match{:template "/api/user/:id"
|
||||
; :meta {:name :user/user}
|
||||
; :path "/api/user/1"
|
||||
; :handler nil
|
||||
; :result nil
|
||||
; :params {:id "1"}}
|
||||
```
|
||||
|
||||
|
|
@ -201,13 +201,13 @@ Path-based routing:
|
|||
; :interceptors [::api ::admin]
|
||||
; :roles #{:root}}
|
||||
; :path "/api/admin/root"
|
||||
; :handler nil
|
||||
; :result nil
|
||||
; :params {}}
|
||||
```
|
||||
|
||||
On match, route meta-data is returned and can interpreted by the application.
|
||||
|
||||
Routers also support meta-data compilation enabling things like fast [Ring](https://github.com/ring-clojure/ring) or [Pedestal](http://pedestal.io/) -style handlers. Compilation results are found under `:handler` in the match. See [configuring routers](#configuring-routers) for details.
|
||||
Routers also support meta-data compilation enabling things like fast [Ring](https://github.com/ring-clojure/ring) or [Pedestal](http://pedestal.io/) -style handlers. Compilation results are found under `:result` in the match. See [configuring routers](#configuring-routers) for details.
|
||||
|
||||
## Route conflicts
|
||||
|
||||
|
|
@ -424,7 +424,7 @@ Routers can be configured via options. Options allow things like [`clojure.spec`
|
|||
| `:meta` | Initial expanded route-meta vector (default `[]`)
|
||||
| `:expand` | Function of `arg opts => meta` to expand route arg to route meta-data (default `reitit.core/expand`)
|
||||
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
|
||||
| `:compile` | Function of `route opts => handler` to compile a route handler
|
||||
| `:compile` | Function of `route opts => result` to compile a route handler
|
||||
| `:conflicts` | Function of `{route #{route}} => side-effect` to handle conflicting routes (default `reitit.core/throw-on-conflicts!`)
|
||||
| `:router` | Function of `routes opts => router` to override the actual router implementation
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
(if (seq childs)
|
||||
(walk-many (str pacc path) macc childs)
|
||||
[[(str pacc path) macc]]))))]
|
||||
(walk-one path meta data)))
|
||||
(walk-one path (mapv identity meta) data)))
|
||||
|
||||
(defn map-meta [f routes]
|
||||
(mapv #(update % 1 f) routes))
|
||||
|
|
@ -100,8 +100,8 @@
|
|||
(match-by-path [this path])
|
||||
(match-by-name [this name] [this name params]))
|
||||
|
||||
(defrecord Match [template meta handler params path])
|
||||
(defrecord PartialMatch [template meta handler params required])
|
||||
(defrecord Match [template meta result params path])
|
||||
(defrecord PartialMatch [template meta result params required])
|
||||
|
||||
(defn partial-match? [x]
|
||||
(instance? PartialMatch x))
|
||||
|
|
@ -132,11 +132,11 @@
|
|||
(let [compiled (map #(compile-route % opts) routes)
|
||||
names (find-names routes opts)
|
||||
[data lookup] (reduce
|
||||
(fn [[data lookup] [p {:keys [name] :as meta} handler]]
|
||||
(let [{:keys [params] :as route} (impl/create [p meta handler])
|
||||
(fn [[data lookup] [p {:keys [name] :as meta} result]]
|
||||
(let [{:keys [params] :as route} (impl/create [p meta result])
|
||||
f #(if-let [path (impl/path-for route %)]
|
||||
(->Match p meta handler % path)
|
||||
(->PartialMatch p meta handler % params))]
|
||||
(->Match p meta result % path)
|
||||
(->PartialMatch p meta result % params))]
|
||||
[(conj data route)
|
||||
(if name (assoc lookup name f) lookup)]))
|
||||
[[] {}] compiled)
|
||||
|
|
@ -155,7 +155,7 @@
|
|||
(reduce
|
||||
(fn [acc ^Route route]
|
||||
(if-let [params ((:matcher route) path)]
|
||||
(reduced (->Match (:path route) (:meta route) (:handler route) params path))))
|
||||
(reduced (->Match (:path route) (:meta route) (:result route) params path))))
|
||||
nil data))
|
||||
(match-by-name [_ name]
|
||||
(if-let [match (impl/fast-get lookup name)]
|
||||
|
|
@ -179,10 +179,10 @@
|
|||
(let [compiled (map #(compile-route % opts) routes)
|
||||
names (find-names routes opts)
|
||||
[data lookup] (reduce
|
||||
(fn [[data lookup] [p {:keys [name] :as meta} handler]]
|
||||
[(assoc data p (->Match p meta handler {} p))
|
||||
(fn [[data lookup] [p {:keys [name] :as meta} result]]
|
||||
[(assoc data p (->Match p meta result {} p))
|
||||
(if name
|
||||
(assoc lookup name #(->Match p meta handler % p))
|
||||
(assoc lookup name #(->Match p meta result % p))
|
||||
lookup)]) [{} {}] compiled)
|
||||
data (impl/fast-map data)
|
||||
lookup (impl/fast-map lookup)]
|
||||
|
|
@ -244,10 +244,10 @@
|
|||
| -------------|-------------|
|
||||
| `:path` | Base-path for routes (default `\"\"`)
|
||||
| `:routes` | Initial resolved routes (default `[]`)
|
||||
| `:meta` | Initial expanded route-meta vector (default `[]`)
|
||||
| `:meta` | Initial route meta (default `{}`)
|
||||
| `:expand` | Function of `arg opts => meta` to expand route arg to route meta-data (default `reitit.core/expand`)
|
||||
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
|
||||
| `:compile` | Function of `route opts => handler` to compile a route handler
|
||||
| `:compile` | Function of `route opts => result` to compile a route handler
|
||||
| `:conflicts` | Function of `{route #{route}} => side-effect` to handle conflicting routes (default `reitit.core/throw-on-conflicts!`)
|
||||
| `:router` | Function of `routes opts => router` to override the actual router implementation"
|
||||
([data]
|
||||
|
|
|
|||
|
|
@ -101,15 +101,15 @@
|
|||
;; Routing (c) Metosin
|
||||
;;
|
||||
|
||||
(defrecord Route [path matcher parts params meta handler])
|
||||
(defrecord Route [path matcher parts params meta result])
|
||||
|
||||
(defn create [[path meta handler]]
|
||||
(defn create [[path meta result]]
|
||||
(if (contains-wilds? path)
|
||||
(as-> (parse-path path) $
|
||||
(assoc $ :path-re (path-regex $))
|
||||
(merge $ {:path path
|
||||
:matcher (path-matcher $)
|
||||
:handler handler
|
||||
:result result
|
||||
:meta meta})
|
||||
(dissoc $ :path-re :path-constraints)
|
||||
(update $ :path-params set)
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
(map->Route {:path path
|
||||
:meta meta
|
||||
:matcher #(if (= path %) {})
|
||||
:handler handler})))
|
||||
:result result})))
|
||||
|
||||
(defn segments [path]
|
||||
(let [ss (-> (str/split path #"/") rest vec)]
|
||||
|
|
|
|||
|
|
@ -1,24 +1,45 @@
|
|||
(ns reitit.middleware
|
||||
(:require [meta-merge.core :refer [meta-merge]]
|
||||
[reitit.core :as reitit]))
|
||||
[reitit.core :as reitit])
|
||||
#?(:clj
|
||||
(:import (clojure.lang IFn AFn))))
|
||||
|
||||
(defprotocol ExpandMiddleware
|
||||
(expand-middleware [this opts]))
|
||||
(expand-middleware [this meta opts]))
|
||||
|
||||
(defrecord MiddlewareGenerator [f args]
|
||||
IFn
|
||||
(invoke [_]
|
||||
(f nil nil))
|
||||
(invoke [_ meta]
|
||||
(f meta nil))
|
||||
(invoke [_ meta opts]
|
||||
(f meta opts))
|
||||
#?(:clj
|
||||
(applyTo [this args]
|
||||
(AFn/applyToHelper this args))))
|
||||
|
||||
(extend-protocol ExpandMiddleware
|
||||
|
||||
#?(:clj clojure.lang.APersistentVector
|
||||
:cljs cljs.core.PersistentVector)
|
||||
(expand-middleware [[f & args] _]
|
||||
(fn [handler]
|
||||
(apply f handler args)))
|
||||
(expand-middleware [[f & args] meta opts]
|
||||
(if-let [mw (expand-middleware f meta opts)]
|
||||
(fn [handler]
|
||||
(apply mw handler args))))
|
||||
|
||||
#?(:clj clojure.lang.Fn
|
||||
:cljs function)
|
||||
(expand-middleware [this _] this)
|
||||
(expand-middleware [this _ _] this)
|
||||
|
||||
MiddlewareGenerator
|
||||
(expand-middleware [this meta opts]
|
||||
(if-let [mw (this meta opts)]
|
||||
(fn [handler & args]
|
||||
(apply mw handler args))))
|
||||
|
||||
nil
|
||||
(expand-middleware [_ _]))
|
||||
(expand-middleware [_ _ _]))
|
||||
|
||||
(defn- ensure-handler! [path meta scope]
|
||||
(when-not (:handler meta)
|
||||
|
|
@ -28,18 +49,22 @@
|
|||
(merge {:path path, :meta meta}
|
||||
(if scope {:scope scope}))))))
|
||||
|
||||
(defn compose-middleware [middleware opts]
|
||||
(defn compose-middleware [middleware meta opts]
|
||||
(->> middleware
|
||||
(keep identity)
|
||||
(map #(expand-middleware % opts))
|
||||
(map #(expand-middleware % meta opts))
|
||||
(keep identity)
|
||||
(apply comp identity)))
|
||||
|
||||
(defn gen [f & args]
|
||||
(->MiddlewareGenerator f args))
|
||||
|
||||
(defn compile-handler
|
||||
([route opts]
|
||||
(compile-handler route opts nil))
|
||||
([[path {:keys [middleware handler] :as meta}] opts scope]
|
||||
(ensure-handler! path meta scope)
|
||||
((compose-middleware middleware opts) handler)))
|
||||
((compose-middleware middleware meta opts) handler)))
|
||||
|
||||
(defn router
|
||||
([data]
|
||||
|
|
|
|||
|
|
@ -60,6 +60,5 @@
|
|||
([data]
|
||||
(router data nil))
|
||||
([data opts]
|
||||
(let [opts (meta-merge {:coerce coerce-handler
|
||||
:compile compile-handler} opts)]
|
||||
(let [opts (meta-merge {:coerce coerce-handler, :compile compile-handler} opts)]
|
||||
(reitit/router data opts))))
|
||||
|
|
|
|||
|
|
@ -96,15 +96,15 @@
|
|||
(reitit/routes router))))
|
||||
(testing "route match contains compiled handler"
|
||||
(is (= 2 @compile-times))
|
||||
(let [{:keys [handler]} (reitit/match-by-path router "/api/pong")]
|
||||
(is handler)
|
||||
(is (= "/api/pong" (handler)))
|
||||
(let [{:keys [result]} (reitit/match-by-path router "/api/pong")]
|
||||
(is result)
|
||||
(is (= "/api/pong" (result)))
|
||||
(is (= 2 @compile-times))))))
|
||||
(testing "default compile"
|
||||
(let [router (reitit/router ["/ping" (constantly "ok")])]
|
||||
(let [{:keys [handler]} (reitit/match-by-path router "/ping")]
|
||||
(is handler)
|
||||
(is (= "ok" (handler)))))))
|
||||
(let [{:keys [result]} (reitit/match-by-path router "/ping")]
|
||||
(is result)
|
||||
(is (= "ok" (result)))))))
|
||||
|
||||
(testing "custom router"
|
||||
(let [router (reitit/router ["/ping"] {:router (fn [_ _]
|
||||
|
|
|
|||
129
test/cljc/reitit/middleware_test.cljc
Normal file
129
test/cljc/reitit/middleware_test.cljc
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
(ns reitit.middleware-test
|
||||
(:require [clojure.test :refer [deftest testing is]]
|
||||
[reitit.middleware :as middleware]
|
||||
[clojure.set :as set]
|
||||
[reitit.core :as reitit])
|
||||
#?(:clj
|
||||
(:import (clojure.lang ExceptionInfo))))
|
||||
|
||||
(defn mw [handler name]
|
||||
(fn
|
||||
([request]
|
||||
(-> request
|
||||
(update ::mw (fnil conj []) name)
|
||||
(handler)
|
||||
(update :body (fnil conj []) name)))
|
||||
([request respond raise]
|
||||
(handler
|
||||
(update request ::mw (fnil conj []) name)
|
||||
#(respond (update % :body (fnil conj []) name))
|
||||
raise))))
|
||||
|
||||
(defn handler
|
||||
([{:keys [::mw]}]
|
||||
{:status 200 :body (conj mw :ok)})
|
||||
([request respond raise]
|
||||
(respond (handler request))))
|
||||
|
||||
(deftest expand-middleware-test
|
||||
(testing "middleware generators"
|
||||
(let [calls (atom 0)]
|
||||
|
||||
(testing "record generator"
|
||||
(reset! calls 0)
|
||||
(let [syntax [(middleware/gen
|
||||
(fn [meta _]
|
||||
(swap! calls inc)
|
||||
(fn [handler value]
|
||||
(swap! calls inc)
|
||||
(fn [request]
|
||||
[meta value request]))))]
|
||||
app ((middleware/compose-middleware syntax :meta {}) identity :value)]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:meta :value :request] (app :request)))
|
||||
(is (= 2 @calls)))))
|
||||
|
||||
(testing "middleware generator as function"
|
||||
(reset! calls 0)
|
||||
(let [syntax (middleware/gen
|
||||
(fn [meta _]
|
||||
(swap! calls inc)
|
||||
(fn [handler value]
|
||||
(swap! calls inc)
|
||||
(fn [request]
|
||||
[meta value request]))))
|
||||
app ((syntax :meta nil) identity :value)]
|
||||
(dotimes [_ 10]
|
||||
(is (= [:meta :value :request] (app :request)))
|
||||
(is (= 2 @calls)))))
|
||||
|
||||
(testing "generator vector"
|
||||
(reset! calls 0)
|
||||
(let [syntax [[(middleware/gen
|
||||
(fn [meta _]
|
||||
(swap! calls inc)
|
||||
(fn [handler value]
|
||||
(swap! calls inc)
|
||||
(fn [request]
|
||||
[meta value request])))) :value]]
|
||||
app ((middleware/compose-middleware syntax :meta {}) identity)]
|
||||
(is (= [:meta :value :request] (app :request)))
|
||||
(dotimes [_ 10]
|
||||
(is (= [:meta :value :request] (app :request)))
|
||||
(is (= 2 @calls)))))
|
||||
|
||||
(testing "generator can return nil"
|
||||
(reset! calls 0)
|
||||
(let [syntax [[(middleware/gen
|
||||
(fn [meta _])) :value]]
|
||||
app ((middleware/compose-middleware syntax :meta {}) identity)]
|
||||
(is (= :request (app :request)))
|
||||
(dotimes [_ 10]
|
||||
(is (= :request (app :request)))))))))
|
||||
|
||||
|
||||
(deftest middleware-router-test
|
||||
|
||||
(testing "all paths should have a handler"
|
||||
(is (thrown-with-msg?
|
||||
ExceptionInfo
|
||||
#"path \"/ping\" doesn't have a :handler defined"
|
||||
(middleware/router ["/ping"]))))
|
||||
|
||||
(testing "ring-handler"
|
||||
(let [api-mw #(mw % :api)
|
||||
router (middleware/router
|
||||
[["/ping" handler]
|
||||
["/api" {:middleware [api-mw]}
|
||||
["/ping" handler]
|
||||
["/admin" {:middleware [[mw :admin]]}
|
||||
["/ping" handler]]]])
|
||||
app (fn
|
||||
([{:keys [uri] :as request}]
|
||||
(if-let [handler (:result (reitit/match-by-path router uri))]
|
||||
(handler request)))
|
||||
([{:keys [uri] :as request} respond raise]
|
||||
(if-let [handler (:result (reitit/match-by-path router uri))]
|
||||
(handler request respond raise))))]
|
||||
|
||||
(testing "not found"
|
||||
(is (= nil (app {:uri "/favicon.ico"}))))
|
||||
|
||||
(testing "normal handler"
|
||||
(is (= {:status 200, :body [:ok]}
|
||||
(app {:uri "/ping"}))))
|
||||
|
||||
(testing "with middleware"
|
||||
(is (= {:status 200, :body [:api :ok :api]}
|
||||
(app {:uri "/api/ping"}))))
|
||||
|
||||
(testing "with nested middleware"
|
||||
(is (= {:status 200, :body [:api :admin :ok :admin :api]}
|
||||
(app {:uri "/api/admin/ping"}))))
|
||||
|
||||
(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)))))))
|
||||
|
|
@ -26,50 +26,6 @@
|
|||
([request respond raise]
|
||||
(respond (handler request))))
|
||||
|
||||
(deftest middleware-router-test
|
||||
|
||||
(testing "all paths should have a handler"
|
||||
(is (thrown-with-msg?
|
||||
ExceptionInfo
|
||||
#"path \"/ping\" doesn't have a :handler defined"
|
||||
(middleware/router ["/ping"]))))
|
||||
|
||||
(testing "ring-handler"
|
||||
(let [api-mw #(mw % :api)
|
||||
router (middleware/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"
|
||||
(is (= router (ring/get-router app))))
|
||||
|
||||
(testing "not found"
|
||||
(is (= nil (app {:uri "/favicon.ico"}))))
|
||||
|
||||
(testing "normal handler"
|
||||
(is (= {:status 200, :body [:ok]}
|
||||
(app {:uri "/ping"}))))
|
||||
|
||||
(testing "with middleware"
|
||||
(is (= {:status 200, :body [:api :ok :api]}
|
||||
(app {:uri "/api/ping"}))))
|
||||
|
||||
(testing "with nested middleware"
|
||||
(is (= {:status 200, :body [:api :admin :ok :admin :api]}
|
||||
(app {:uri "/api/admin/ping"}))))
|
||||
|
||||
(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)))))))
|
||||
|
||||
|
||||
(deftest ring-router-test
|
||||
|
||||
(testing "all paths should have a handler"
|
||||
|
|
@ -142,7 +98,7 @@
|
|||
|
||||
(testing "only top-level route names are matched"
|
||||
(is (= [::all ::get ::users]
|
||||
(reitit/route-names router))))
|
||||
(reitit/route-names router))))
|
||||
|
||||
(testing "all named routes can be matched"
|
||||
(doseq [name (reitit/route-names router)]
|
||||
|
|
|
|||
Loading…
Reference in a new issue