diff --git a/perf-test/clj/reitit/perf_test.clj b/perf-test/clj/reitit/perf_test.clj index c4d1345e..3c08d1cf 100644 --- a/perf-test/clj/reitit/perf_test.clj +++ b/perf-test/clj/reitit/perf_test.clj @@ -66,9 +66,48 @@ ["/auth/recovery/token/:token" :auth/recovery] ["/workspace/:project/:page" :workspace/page]])) -(defn routing-test [] +(defn routing-test1 [] - (suite "simple routing") + (suite "static route") + + ;; 2.2µs + (title "bidi") + (let [call #(bidi/match-route bidi-routes "/auth/login")] + (assert (call)) + (cc/quick-bench + (call))) + + ;; 1.5µs (-40%) + (title "ataraxy") + (let [call #(ataraxy/matches ataraxy-routes {:uri "/auth/login"})] + (assert (call)) + (cc/quick-bench + (call))) + + ;; 1.1µs (-50%) + (title "pedestal - map-tree => prefix-tree") + (let [call #(pedestal/find-route pedestal-router {:path-info "/auth/login" :request-method :get})] + (assert (call)) + (cc/quick-bench + (call))) + + ;; 1.5µs (-40%) + (title "compojure-api") + (let [call #(compojure-api-routes {:uri "/auth/login", :request-method :get})] + (assert (call)) + (cc/quick-bench + (call))) + + ;; 11.5ns (-99,5%) + (title "reitit") + (let [call #(reitit/match-by-path reitit-routes "/auth/login")] + (assert (call)) + (cc/quick-bench + (call)))) + +(defn routing-test2 [] + + (suite "wildcard route") ;; 15.4µs (title "bidi") @@ -141,5 +180,6 @@ (call)))) (comment - (routing-test) + (routing-test1) + (routing-test2) (reverse-routing-test)) diff --git a/src/reitit/core.cljc b/src/reitit/core.cljc index 652abcca..9b39df0c 100644 --- a/src/reitit/core.cljc +++ b/src/reitit/core.cljc @@ -213,17 +213,52 @@ (if-let [match (impl/fast-get lookup name)] (match params))))))) +(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)) + (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 meta result] :as compiled] (compile-routes routes opts) + p #?(:clj (.intern ^String p) :cljs p) + match (->Match p meta result {} p)] + (reify Router + (router-name [_] + :single-static-path-router) + (routes [_] + compiled) + (options [_] + opts) + (route-names [_] + names) + (match-by-path [_ path] + (if (#?(:clj .equals :cljs =) p path) + match)) + (match-by-name [_ name] + (if (= n name) + match)) + (match-by-name [_ name params] + (if (= n name) + (impl/fast-assoc match :params params))))))) + (defn mixed-router - "Creates two routers: [[lookup-router]] for static routes and - [[linear-router]] for wildcard routes. All routes should be - non-conflicting. Takes resolved routes and optional + "Creates two routers: [[lookup-router]] or [[single-static-path-router]] for + static routes and [[linear-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 [{linear true, lookup false} (group-by impl/wild-route? routes) - linear-router (linear-router linear opts) - lookup-router (lookup-router lookup opts) + ->static-router (if (= 1 (count lookup)) single-static-path-router lookup-router) + wildcard-router (linear-router linear opts) + static-router (->static-router lookup opts) names (find-names routes opts)] (reify Router (router-name [_] @@ -235,19 +270,19 @@ (route-names [_] names) (match-by-path [_ path] - (or (match-by-path lookup-router path) - (match-by-path linear-router path))) + (or (match-by-path static-router path) + (match-by-path wildcard-router path))) (match-by-name [_ name] - (or (match-by-name lookup-router name) - (match-by-name linear-router name))) + (or (match-by-name static-router name) + (match-by-name wildcard-router name))) (match-by-name [_ name params] - (or (match-by-name lookup-router name params) - (match-by-name linear-router name params))))))) + (or (match-by-name static-router name params) + (match-by-name wildcard-router name params))))))) (defn router "Create a [[Router]] from raw route data and optionally an options map. - If routes contain wildcards, a [[LinearRouter]] is used, otherwise a - [[LookupRouter]]. The following options are available: + Selects implementation based on route details. The following options + are available: | key | description | | -------------|-------------| @@ -265,10 +300,11 @@ (let [{:keys [router] :as opts} (meta-merge default-router-options opts) routes (resolve-routes data opts) conflicting (conflicting-routes routes) - wilds? (some impl/wild-route? routes) + wilds? (boolean (some impl/wild-route? routes)) all-wilds? (every? impl/wild-route? routes) router (cond router router + (and (= 1 (count routes)) (not wilds?)) single-static-path-router (not wilds?) lookup-router all-wilds? linear-router (not conflicting) mixed-router diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index 6a50c46c..669b988c 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -70,6 +70,44 @@ (r/resolve-routes ["/api/:version/ping"] {}))))))) + (testing "single-static-path-router" + (let [router (r/router ["/api" ["/ipa" ["/large" ::beer]]])] + (is (= :single-static-path-router (r/router-name router))) + (is (= [["/api/ipa/large" {:name ::beer} nil]] + (r/routes router))) + (is (= true (map? (r/options router)))) + (is (= (r/map->Match + {:template "/api/ipa/large" + :meta {:name ::beer} + :path "/api/ipa/large" + :params {}}) + (r/match-by-path router "/api/ipa/large"))) + (is (= (r/map->Match + {:template "/api/ipa/large" + :meta {:name ::beer} + :path "/api/ipa/large" + :params {:size "large"}}) + (r/match-by-name router ::beer {:size "large"}))) + (is (= nil (r/match-by-name router "ILLEGAL"))) + (is (= [::beer] (r/route-names router))) + + (testing "can't be created with wildcard routes" + (is (thrown-with-msg? + ExceptionInfo + #":single-static-path-router requires exactly 1 static route" + (r/single-static-path-router + (r/resolve-routes + ["/api/:version/ping"] {}))))) + + (testing "can't be created with multiple routes" + (is (thrown-with-msg? + ExceptionInfo + #":single-static-path-router requires exactly 1 static route" + (r/single-static-path-router + (r/resolve-routes + [["/ping"] + ["/pong"]] {}))))))) + (testing "route coercion & compilation" (testing "custom compile"