mirror of
https://github.com/metosin/reitit.git
synced 2025-12-23 18:41:11 +00:00
commit
f180204125
10 changed files with 152 additions and 47 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
* **BREAKING**: the router option key to extract body format has been renamed: `:extract-request-format` => `:reitit.coercion/extract-request-format`
|
||||
* should only concern you if you are not using [Muuntaja](https://github.com/metosin/muuntaja).
|
||||
* the `r/routes` returns just the path + data tuples as documented, not the compiled route results. To get the compiled results, use `r/compiled-routes` instead.
|
||||
|
||||
## `reitit-swagger-ui`
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
* [Data-specs](coercion/data_spec_coercion.md)
|
||||
* [Advanced](advanced/README.md)
|
||||
* [Configuring Routers](advanced/configuring_routers.md)
|
||||
* [Composing Routers](advanced/composing_routers.md)
|
||||
* [Different Routers](advanced/different_routers.md)
|
||||
* [Route Validation](advanced/route_validation.md)
|
||||
* [Dev Workflow](advanced/dev_workflow.md)
|
||||
|
|
@ -30,6 +31,10 @@
|
|||
* [Route Data Validation](ring/route_data_validation.md)
|
||||
* [Compiling Middleware](ring/compiling_middleware.md)
|
||||
* [Swagger Support](ring/swagger.md)
|
||||
* [Frontend](frontend/README.md)
|
||||
* [Basics](frontend/basics.md)
|
||||
* [Browser integration](frontend/browser.md)
|
||||
* [Controllers](frontend/controllers.md)
|
||||
* [Performance](performance.md)
|
||||
* [Interceptors (WIP)](interceptors.md)
|
||||
* [Development Instructions](development.md)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Advanced
|
||||
|
||||
* [Configuring Routers](configuring_routers.md)
|
||||
* [Composing Routers](composing_routers.md)
|
||||
* [Different Routers](different_routers.md)
|
||||
* [Route Validation](route_validation.md)
|
||||
|
|
|
|||
79
doc/advanced/composing_routers.md
Normal file
79
doc/advanced/composing_routers.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Composing Routers
|
||||
|
||||
Routers expose both their routes and options via the `Router` protocol, enabling one to create new routers from existing ones.
|
||||
|
||||
## Adding routes to an existing routers
|
||||
|
||||
Let's define a router in an `Atom`:
|
||||
|
||||
```clj
|
||||
(require '[reitit.core :as r])
|
||||
|
||||
(def router (atom (r/router
|
||||
[["/foo/bar" identity]
|
||||
["/foo/bar/:id" identity]])))
|
||||
|
||||
(r/routes @router)
|
||||
;[["/foo/bar" {:handler #object[clojure.core$identity]}]
|
||||
; ["/foo/bar/:id" {:handler #object[clojure.core$identity]}]]
|
||||
```
|
||||
|
||||
A helper to add new route to a router:
|
||||
|
||||
```clj
|
||||
(defn add-route [router route]
|
||||
(r/router
|
||||
(conj (r/routes router) route)
|
||||
(r/options router)))
|
||||
```
|
||||
|
||||
Now, we can add routers to the router:
|
||||
|
||||
```clj
|
||||
(swap! router add-route ["/foo/bar/:id/:subid" identity])
|
||||
|
||||
(r/routes @router)
|
||||
;[["/foo/bar" {:handler #object[clojure.core$identity]}]
|
||||
; ["/foo/bar/:id" {:handler #object[clojure.core$identity]}]
|
||||
; ["/foo/bar/:id/:subid" {:handler #object[clojure.core$identity]}]]
|
||||
```
|
||||
|
||||
Router is recreated, so all the rules are fires:
|
||||
|
||||
```clj
|
||||
(swap! router add-route ["/foo/:fail" identity])
|
||||
;CompilerException clojure.lang.ExceptionInfo: Router contains conflicting routes:
|
||||
;
|
||||
; /foo/bar
|
||||
;-> /foo/:fail
|
||||
```
|
||||
|
||||
## Merging routers
|
||||
|
||||
Given we have two routers:
|
||||
|
||||
```clj
|
||||
(def r1 (r/router ["/route1" identity]))
|
||||
(def r2 (r/router ["/route2" identity]))
|
||||
```
|
||||
|
||||
We can create a new router, with merged routes and options:
|
||||
|
||||
```clj
|
||||
(def r12 (r/router
|
||||
(merge
|
||||
(r/routes r1)
|
||||
(r/routes r2))
|
||||
(merge
|
||||
(r/options r1)
|
||||
(r/options r2))))
|
||||
|
||||
(r/routes r12)
|
||||
;[["/route1" {:handler #object[clojure.core$identity]}]
|
||||
; ["/route2" {:handler #object[clojure.core$identity]}]]
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
* `reitit.core/merge-routes` to effectively merge routes with route data
|
||||
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
{:file "doc/frontend/README.md"}
|
||||
["Basics" {:file "doc/frontend/basics.md"}]
|
||||
["Browser integration" {:file "doc/frontend/browser.md"}]
|
||||
["Controllers" {:file "doc/frontend/controllers/.md"}]]
|
||||
["Controllers" {:file "doc/frontend/controllers.md"}]]
|
||||
["Performance" {:file "doc/performance.md"}]
|
||||
["Interceptors (WIP)" {:file "doc/interceptors.md"}]
|
||||
["Development Instructions" {:file "doc/development.md"}]
|
||||
|
|
|
|||
|
|
@ -99,12 +99,16 @@
|
|||
(defn- compile-routes [routes opts]
|
||||
(into [] (keep #(compile-route % opts) routes)))
|
||||
|
||||
(defn- uncompile-routes [routes]
|
||||
(mapv (comp vec (partial take 2)) routes))
|
||||
|
||||
(defn route-info [route]
|
||||
(select-keys (impl/create route) [:path :path-parts :path-params :result :data]))
|
||||
|
||||
(defprotocol Router
|
||||
(router-name [this])
|
||||
(routes [this])
|
||||
(compiled-routes [this])
|
||||
(options [this])
|
||||
(route-names [this])
|
||||
(match-by-path [this path])
|
||||
|
|
@ -145,10 +149,10 @@
|
|||
(defn linear-router
|
||||
"Creates a linear-router from resolved routes and optional
|
||||
expanded options. See [[router]] for available options"
|
||||
([routes]
|
||||
(linear-router routes {}))
|
||||
([routes opts]
|
||||
(let [names (find-names routes opts)
|
||||
([compiled-routes]
|
||||
(linear-router compiled-routes {}))
|
||||
([compiled-routes opts]
|
||||
(let [names (find-names compiled-routes opts)
|
||||
[pl nl] (reduce
|
||||
(fn [[pl nl] [p {:keys [name] :as data} result]]
|
||||
(let [{:keys [path-params] :as route} (impl/create [p data result])
|
||||
|
|
@ -157,8 +161,9 @@
|
|||
(->PartialMatch p data result % path-params))]
|
||||
[(conj pl route)
|
||||
(if name (assoc nl name f) nl)]))
|
||||
[[] {}] routes)
|
||||
lookup (impl/fast-map nl)]
|
||||
[[] {}] compiled-routes)
|
||||
lookup (impl/fast-map nl)
|
||||
routes (uncompile-routes compiled-routes)]
|
||||
^{:type ::router}
|
||||
(reify
|
||||
Router
|
||||
|
|
@ -166,6 +171,8 @@
|
|||
:linear-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
|
|
@ -186,30 +193,33 @@
|
|||
(defn lookup-router
|
||||
"Creates a lookup-router from resolved routes and optional
|
||||
expanded options. See [[router]] for available options"
|
||||
([routes]
|
||||
(lookup-router routes {}))
|
||||
([routes opts]
|
||||
(when-let [wilds (seq (filter impl/wild-route? routes))]
|
||||
([compiled-routes]
|
||||
(lookup-router compiled-routes {}))
|
||||
([compiled-routes opts]
|
||||
(when-let [wilds (seq (filter impl/wild-route? compiled-routes))]
|
||||
(throw
|
||||
(ex-info
|
||||
(str "can't create :lookup-router with wildcard routes: " wilds)
|
||||
{:wilds wilds
|
||||
:routes routes})))
|
||||
(let [names (find-names routes opts)
|
||||
:routes compiled-routes})))
|
||||
(let [names (find-names compiled-routes opts)
|
||||
[pl nl] (reduce
|
||||
(fn [[pl nl] [p {:keys [name] :as data} result]]
|
||||
[(assoc pl p (->Match p data result {} p))
|
||||
(if name
|
||||
(assoc nl name #(->Match p data result % p))
|
||||
nl)]) [{} {}] routes)
|
||||
nl)]) [{} {}] compiled-routes)
|
||||
data (impl/fast-map pl)
|
||||
lookup (impl/fast-map nl)]
|
||||
lookup (impl/fast-map nl)
|
||||
routes (uncompile-routes compiled-routes)]
|
||||
^{:type ::router}
|
||||
(reify Router
|
||||
(router-name [_]
|
||||
:lookup-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
|
|
@ -226,10 +236,10 @@
|
|||
(defn segment-router
|
||||
"Creates a special prefix-tree style segment router from resolved routes and optional
|
||||
expanded options. See [[router]] for available options"
|
||||
([routes]
|
||||
(segment-router routes {}))
|
||||
([routes opts]
|
||||
(let [names (find-names routes opts)
|
||||
([compiled-routes]
|
||||
(segment-router compiled-routes {}))
|
||||
([compiled-routes opts]
|
||||
(let [names (find-names compiled-routes opts)
|
||||
[pl nl] (reduce
|
||||
(fn [[pl nl] [p {:keys [name] :as data} result]]
|
||||
(let [{:keys [path-params] :as route} (impl/create [p data result])
|
||||
|
|
@ -238,8 +248,9 @@
|
|||
(->PartialMatch p data result % path-params))]
|
||||
[(segment/insert pl p (->Match p data result nil nil))
|
||||
(if name (assoc nl name f) nl)]))
|
||||
[nil {}] routes)
|
||||
lookup (impl/fast-map nl)]
|
||||
[nil {}] compiled-routes)
|
||||
lookup (impl/fast-map nl)
|
||||
routes (uncompile-routes compiled-routes)]
|
||||
^{:type ::router}
|
||||
(reify
|
||||
Router
|
||||
|
|
@ -247,6 +258,8 @@
|
|||
:segment-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
|
|
@ -266,24 +279,27 @@
|
|||
(defn single-static-path-router
|
||||
"Creates a fast router of 1 static route(s) and optional
|
||||
expanded options. See [[router]] for available options"
|
||||
([routes]
|
||||
(single-static-path-router routes {}))
|
||||
([routes opts]
|
||||
(when (or (not= (count routes) 1) (some impl/wild-route? routes))
|
||||
([compiled-routes]
|
||||
(single-static-path-router compiled-routes {}))
|
||||
([compiled-routes opts]
|
||||
(when (or (not= (count compiled-routes) 1) (some impl/wild-route? compiled-routes))
|
||||
(throw
|
||||
(ex-info
|
||||
(str ":single-static-path-router requires exactly 1 static route: " routes)
|
||||
{:routes routes})))
|
||||
(let [[n :as names] (find-names routes opts)
|
||||
[[p data result] :as compiled] routes
|
||||
(str ":single-static-path-router requires exactly 1 static route: " compiled-routes)
|
||||
{:routes compiled-routes})))
|
||||
(let [[n :as names] (find-names compiled-routes opts)
|
||||
[[p data result] :as compiled] compiled-routes
|
||||
p #?(:clj (.intern ^String p) :cljs p)
|
||||
match (->Match p data result {} p)]
|
||||
match (->Match p data result {} p)
|
||||
routes (uncompile-routes compiled-routes)]
|
||||
^{:type ::router}
|
||||
(reify Router
|
||||
(router-name [_]
|
||||
:single-static-path-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
|
|
@ -303,20 +319,23 @@
|
|||
static routes and [[segment-router]] for wildcard routes. All
|
||||
routes should be non-conflicting. Takes resolved routes and optional
|
||||
expanded options. See [[router]] for options."
|
||||
([routes]
|
||||
(mixed-router routes {}))
|
||||
([routes opts]
|
||||
(let [{wild true, lookup false} (group-by impl/wild-route? routes)
|
||||
([compiled-routes]
|
||||
(mixed-router compiled-routes {}))
|
||||
([compiled-routes opts]
|
||||
(let [{wild true, lookup false} (group-by impl/wild-route? compiled-routes)
|
||||
->static-router (if (= 1 (count lookup)) single-static-path-router lookup-router)
|
||||
wildcard-router (segment-router wild opts)
|
||||
static-router (->static-router lookup opts)
|
||||
names (find-names routes opts)]
|
||||
names (find-names compiled-routes opts)
|
||||
routes (uncompile-routes compiled-routes)]
|
||||
^{:type ::router}
|
||||
(reify Router
|
||||
(router-name [_]
|
||||
:mixed-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
|
|
@ -354,21 +373,21 @@
|
|||
(let [{:keys [router] :as opts} (meta-merge default-router-options opts)
|
||||
routes (resolve-routes raw-routes opts)
|
||||
conflicting (conflicting-routes routes)
|
||||
routes (compile-routes routes opts)
|
||||
wilds? (boolean (some impl/wild-route? routes))
|
||||
all-wilds? (every? impl/wild-route? routes)
|
||||
compiled-routes (compile-routes routes opts)
|
||||
wilds? (boolean (some impl/wild-route? compiled-routes))
|
||||
all-wilds? (every? impl/wild-route? compiled-routes)
|
||||
router (cond
|
||||
router router
|
||||
(and (= 1 (count routes)) (not wilds?)) single-static-path-router
|
||||
(and (= 1 (count compiled-routes)) (not wilds?)) single-static-path-router
|
||||
conflicting linear-router
|
||||
(not wilds?) lookup-router
|
||||
all-wilds? segment-router
|
||||
:else mixed-router)]
|
||||
|
||||
(when-let [validate (:validate opts)]
|
||||
(validate routes opts))
|
||||
(validate compiled-routes opts))
|
||||
|
||||
(when-let [conflicts (:conflicts opts)]
|
||||
(when conflicting (conflicts conflicting)))
|
||||
|
||||
(router routes opts))))
|
||||
(router compiled-routes opts))))
|
||||
|
|
|
|||
|
|
@ -93,6 +93,6 @@
|
|||
(if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))]
|
||||
[(path->template p) endpoint]))]
|
||||
(if id
|
||||
(let [paths (->> router (r/routes) (filter accept-route) (map transform-path) (into {}))]
|
||||
(let [paths (->> router (r/compiled-routes) (filter accept-route) (map transform-path) (into {}))]
|
||||
{:status 200
|
||||
:body (meta-merge swagger {:paths paths})})))))
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
(testing "simple"
|
||||
(let [router (r/router ["/api" ["/ipa" ["/:size" ::beer]]] {:router r})]
|
||||
(is (= name (r/router-name router)))
|
||||
(is (= [["/api/ipa/:size" {:name ::beer} nil]]
|
||||
(is (= [["/api/ipa/:size" {:name ::beer}]]
|
||||
(r/routes router)))
|
||||
(is (map? (r/options router)))
|
||||
(is (= (r/map->Match
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
(are [r name]
|
||||
(let [router (r/router ["/api" ["/ipa" ["/large" ::beer]]] {:router r})]
|
||||
(is (= name (r/router-name router)))
|
||||
(is (= [["/api/ipa/large" {:name ::beer} nil]]
|
||||
(is (= [["/api/ipa/large" {:name ::beer}]]
|
||||
(r/routes router)))
|
||||
(is (map? (r/options router)))
|
||||
(is (= (r/map->Match
|
||||
|
|
@ -141,7 +141,7 @@
|
|||
["/api/pong" {:name ::pong
|
||||
:path "/api/pong",
|
||||
:roles #{:admin}}]]
|
||||
(map butlast (r/routes router)))))
|
||||
(r/routes router))))
|
||||
|
||||
(testing "route match contains compiled handler"
|
||||
(is (= 2 @compile-times))
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@
|
|||
(is (= [::i1 ::i3 :ok ::i3 ::i1] (app "/api")))
|
||||
|
||||
(testing "routes contain list of actually applied interceptors"
|
||||
(is (= [::i1 ::i3 ::api] (->> (r/routes router)
|
||||
(is (= [::i1 ::i3 ::api] (->> (r/compiled-routes router)
|
||||
first
|
||||
last
|
||||
:interceptors
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@
|
|||
(is (= [::mw1 ::mw3 :ok ::mw3 ::mw1] (app "/api")))
|
||||
|
||||
(testing "routes contain list of actually applied mw"
|
||||
(is (= [::mw1 ::mw3] (->> (r/routes router)
|
||||
(is (= [::mw1 ::mw3] (->> (r/compiled-routes router)
|
||||
first
|
||||
last
|
||||
:middleware
|
||||
|
|
|
|||
Loading…
Reference in a new issue