diff --git a/CHANGELOG.md b/CHANGELOG.md index 446d42e6..ea706e1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/doc/SUMMARY.md b/doc/SUMMARY.md index c3879529..ac3e50b1 100644 --- a/doc/SUMMARY.md +++ b/doc/SUMMARY.md @@ -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) diff --git a/doc/advanced/README.md b/doc/advanced/README.md index cad41444..6069fb9f 100644 --- a/doc/advanced/README.md +++ b/doc/advanced/README.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) diff --git a/doc/advanced/composing_routers.md b/doc/advanced/composing_routers.md new file mode 100644 index 00000000..b56d58b4 --- /dev/null +++ b/doc/advanced/composing_routers.md @@ -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 + diff --git a/doc/cljdoc.edn b/doc/cljdoc.edn index ffc68d25..d6bc56c4 100644 --- a/doc/cljdoc.edn +++ b/doc/cljdoc.edn @@ -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"}] diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index 2a22f1a7..88ae1f32 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -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)))) diff --git a/modules/reitit-swagger/src/reitit/swagger.cljc b/modules/reitit-swagger/src/reitit/swagger.cljc index 35a7921e..082a6253 100644 --- a/modules/reitit-swagger/src/reitit/swagger.cljc +++ b/modules/reitit-swagger/src/reitit/swagger.cljc @@ -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})}))))) diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index f1a27584..df3c4278 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -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)) diff --git a/test/cljc/reitit/interceptor_test.cljc b/test/cljc/reitit/interceptor_test.cljc index bb571c8c..ded650c5 100644 --- a/test/cljc/reitit/interceptor_test.cljc +++ b/test/cljc/reitit/interceptor_test.cljc @@ -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 diff --git a/test/cljc/reitit/middleware_test.cljc b/test/cljc/reitit/middleware_test.cljc index 39253be8..ee5c7277 100644 --- a/test/cljc/reitit/middleware_test.cljc +++ b/test/cljc/reitit/middleware_test.cljc @@ -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