From 48961c9ed4f973e91f6cd1de1b5b881203d0692c Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Sat, 22 Dec 2018 10:37:18 +0200 Subject: [PATCH] quarantine-router --- CHANGELOG.md | 12 +++++++ doc/advanced/different_routers.md | 1 + modules/reitit-core/src/reitit/core.cljc | 44 +++++++++++++++++++++++- test/cljc/reitit/core_test.cljc | 6 ++-- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2a5a6a0..821cea66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ * `segment-router` doesn't accept empty segments as path-parameters, fixes [#181](https://github.com/metosin/reitit/issues/181). * path-params are decoded correctly with `r/match-by-name`, fixes [#192](https://github.com/metosin/reitit/issues/192). +* new `:quarantine-router`, which is uses by default if there are any path conflicts: uses internally `:mixed-router` for non-conflicting routes and `:linear-router` for conflicting routes. + +```clj +(-> [["/joulu/kinkku"] ;; linear router + ["/joulu/:torttu"] ;; linear router + ["/tonttu/:id"] ;; segment-router + ["/manna/puuro"] ;; lookup-router + ["/sinappi/silli"]] ;; lookup-router + (r/router {:conflicts nil}) + (r/router-name)) +; => :quarantine-router +``` * updated deps: diff --git a/doc/advanced/different_routers.md b/doc/advanced/different_routers.md index f3189e76..24f205b4 100644 --- a/doc/advanced/different_routers.md +++ b/doc/advanced/different_routers.md @@ -9,6 +9,7 @@ Reitit ships with several different implementations for the `Router` protocol, o | `:lookup-router` | Fast router, uses hash-lookup to resolve the route. Valid if no paths have path or catch-all parameters and there are no [Route conflicts](../basics/route_conflicts.md). | `:single-static-path-router` | Super fast router: string-matches a route. Valid only if there is one static route. | `:mixed-router` | Contains two routers: `:segment-router` for wildcard routes and a `:lookup-router` or `:single-static-path-router` for static routes. Valid only if there are no [Route conflicts](../basics/route_conflicts.md). +| `:quarantine-router` | Contains two routers: `:mixed-router` for non-conflicting routes and a `:linear-router` for conflicting routes. The router name can be asked from the router: diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index 78abb80b..df2f31c0 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -77,6 +77,12 @@ (seq) (into {}))) +(defn conflicting-paths [conflicts] + (->> (for [[p pc] conflicts] + (conj (map first pc) (first p))) + (apply concat) + (set))) + (defn path-conflicts-str [conflicts] (apply str "Router contains conflicting route paths:\n\n" (mapv @@ -373,6 +379,42 @@ (or (match-by-name static-router name path-params) (match-by-name wildcard-router name path-params))))))) +(defn quarantine-router + "Creates two routers: [[mixed-router]] for non-conflicting routes + and [[linear-router]] for conflicting routes. Takes resolved routes + and optional expanded options. See [[router]] for options." + ([compiled-routes] + (quarantine-router compiled-routes {})) + ([compiled-routes opts] + (let [conflicting-paths (-> compiled-routes path-conflicting-routes conflicting-paths) + conflicting? #(contains? conflicting-paths (first %)) + {conflicting true, non-conflicting false} (group-by conflicting? compiled-routes) + linear-router (linear-router conflicting opts) + mixed-router (mixed-router non-conflicting opts) + names (find-names compiled-routes opts) + routes (uncompile-routes compiled-routes)] + ^{:type ::router} + (reify Router + (router-name [_] + :quarantine-router) + (routes [_] + routes) + (compiled-routes [_] + compiled-routes) + (options [_] + opts) + (route-names [_] + names) + (match-by-path [_ path] + (or (match-by-path mixed-router path) + (match-by-path linear-router path))) + (match-by-name [_ name] + (or (match-by-name mixed-router name) + (match-by-name linear-router name))) + (match-by-name [_ name path-params] + (or (match-by-name mixed-router name path-params) + (match-by-name linear-router name path-params))))))) + (defn router "Create a [[Router]] from raw route data and optionally an options map. Selects implementation based on route details. The following options @@ -403,7 +445,7 @@ router (cond router router (and (= 1 (count compiled-routes)) (not wilds?)) single-static-path-router - path-conflicting linear-router + path-conflicting quarantine-router (not wilds?) lookup-router all-wilds? segment-router :else mixed-router)] diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index 2337e0e8..5bfe9c13 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -98,7 +98,8 @@ r/linear-router :linear-router r/segment-router :segment-router - r/mixed-router :mixed-router)) + r/mixed-router :mixed-router + r/quarantine-router :quarantine-router)) (testing "routers handling static paths" (are [r name] @@ -134,7 +135,8 @@ r/single-static-path-router :single-static-path-router r/linear-router :linear-router r/segment-router :segment-router - r/mixed-router :mixed-router)) + r/mixed-router :mixed-router + r/quarantine-router :quarantine-router)) (testing "nil routes are stripped" (is (= [] (r/routes (r/router nil))))