diff --git a/README.md b/README.md index 817b1126..9bf269e9 100644 --- a/README.md +++ b/README.md @@ -400,7 +400,8 @@ Routers can be configured via options. Options allow things like [`clojure.spec` | `: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 - | `:conflicts` | Function of `{route #{route}} => side-effect` to handle conflicting routes (default `reitit.core/throw-on-conflicts!`)" + | `: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 ## Special thanks diff --git a/perf-test/clj/reitit/opensensors_routing_test.clj b/perf-test/clj/reitit/opensensors_routing_test.clj index d25b7942..8c2570d9 100644 --- a/perf-test/clj/reitit/opensensors_routing_test.clj +++ b/perf-test/clj/reitit/opensensors_routing_test.clj @@ -120,7 +120,7 @@ ["/v1/orgs/:org-id/errors" {:handler handler, :name :test/route17}] ["/v1/public/orgs/:org-id" {:handler handler, :name :test/route18}] ["/v1/orgs/:org-id/invitations" {:handler handler, :name :test/route19}] - ["/v2/public/messages/dataset/bulk" {:handler handler, :name :test/route20}] + #_["/v2/public/messages/dataset/bulk" {:handler handler, :name :test/route20}] #_["/v1/users/:user-id/devices/bulk" {:handler handler, :name :test/route21}] ["/v1/users/:user-id/device-errors" {:handler handler, :name :test/route22}] ["/v2/login" {:handler handler, :name :test/route23}] @@ -208,7 +208,7 @@ "" :test/route45}} "public/" {["projects/" :project-id] {"/datasets" :test/route3 "" :test/route27} - "messages/dataset/bulk" :test/route20 + #_#_"messages/dataset/bulk" :test/route20 ["datasets/" :dataset-id] :test/route28 ["messages/dataset/" :dataset-id] :test/route53} ["datasets/" :dataset-id] :test/route11 @@ -268,7 +268,7 @@ "" [:test/route45 user-id]}} "public/" {["projects/" project-id] {"/datasets" [:test/route3 project-id] "" [:test/route27 project-id]} - "messages/dataset/bulk" [:test/route20] + #_#_"messages/dataset/bulk" [:test/route20] ["datasets/" dataset-id] [:test/route28 dataset-id] ["messages/dataset/" dataset-id] [:test/route53 dataset-id]} ["datasets/" dataset-id] [:test/route11 dataset-id] @@ -344,7 +344,7 @@ (context "/projects/:project-id" [] (ANY "/datasets" [] {:name :test/route3} handler) (ANY "/" [] {:name :test/route27} handler)) - (ANY "/messages/dataset/bulk" [] {:name :test/route20} handler) + #_(ANY "/messages/dataset/bulk" [] {:name :test/route20} handler) (ANY "/datasets/:dataset-id" [] {:name :test/route28} handler) (ANY "/messages/dataset/:dataset-id" [] {:name :test/route53} handler)) (ANY "/datasets/:dataset-id" [] {:name :test/route11} handler) @@ -376,7 +376,7 @@ ["/v1/orgs/:org-id/errors" :get handler :route-name :test/route17] ["/v1/public/orgs/:org-id" :get handler :route-name :test/route18] ["/v1/orgs/:org-id/invitations" :get handler :route-name :test/route19] - ["/v2/public/messages/dataset/bulk" :get handler :route-name :test/route20] + #_["/v2/public/messages/dataset/bulk" :get handler :route-name :test/route20] #_["/v1/users/:user-id/devices/bulk" :get handler :route-name :test/route21] ["/v1/users/:user-id/device-errors" :get handler :route-name :test/route22] ["/v2/login" :get handler :route-name :test/route23] @@ -493,12 +493,12 @@ compojure-api-f #(opensensors-compojure-api-routes {:uri % :request-method :get}) pedestal-f #(pedestal/find-route opensensors-pedestal-routes {:path-info % :request-method :get})] - (bench! routes true "reitit" reitit-f) ;; 2538ns 10% - (bench! routes true "pedestal" pedestal-f) ;; 2737ns 11% - (bench! routes true "reitit-ring" reitit-ring-f) ;; 2845ns 11% - (bench! routes true "compojure-api" compojure-api-f) ;; 10215ns 41% - (bench! routes true "bidi" bidi-f) ;; 19298ns 77% - (bench! routes true "ataraxy" ataraxy-f) ;; 24950ns 100% + (bench! routes true "reitit" reitit-f) ;; 2538ns -> 2028ns + (bench! routes true "reitit-ring" reitit-ring-f) ;; 2845ns -> 2299ns + (bench! routes true "pedestal" pedestal-f) ;; 2737ns + (bench! routes true "compojure-api" compojure-api-f) ;; 9823ns + (bench! routes true "bidi" bidi-f) ;; 16716ns + (bench! routes true "ataraxy" ataraxy-f) ;; 24467ns )) diff --git a/src/reitit/core.cljc b/src/reitit/core.cljc index 65d2135e..71d5ae87 100644 --- a/src/reitit/core.cljc +++ b/src/reitit/core.cljc @@ -203,6 +203,37 @@ (if-let [match (impl/fast-get lookup name)] (match 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 + 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) + names (find-names routes opts)] + (reify Router + (router-type [_] + :mixed-router) + (routes [_] + routes) + (options [_] + opts) + (route-names [_] + names) + (match-by-path [_ path] + (or (match-by-path lookup-router path) + (match-by-path linear-router path))) + (match-by-name [_ name] + (or (match-by-name lookup-router name) + (match-by-name linear-router name))) + (match-by-name [_ name params] + (or (match-by-name lookup-router name params) + (match-by-name linear-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 @@ -216,12 +247,21 @@ | `: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 - | `:conflicts` | Function of `{route #{route}} => side-effect` to handle conflicting routes (default `reitit.core/throw-on-conflicts!`)" + | `: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] (router data {})) ([data opts] - (let [opts (meta-merge default-router-options opts) - routes (resolve-routes data opts)] + (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) + router (cond + router router + (not wilds?) lookup-router + (not conflicting) mixed-router + :else linear-router)] + (when-let [conflicts (:conflicts opts)] (when conflicting (conflicts conflicting))) diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index 6034767d..7fe61629 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -1,15 +1,15 @@ (ns reitit.core-test (:require [clojure.test :refer [deftest testing is are]] - [reitit.core :as reitit #?@(:cljs [:refer [Match]])]) + [reitit.core :as reitit #?@(:cljs [:refer [Match Router]])]) #?(:clj - (:import (reitit.core Match) + (:import (reitit.core Match Router) (clojure.lang ExceptionInfo)))) (deftest reitit-test - (testing "linear router" + (testing "mixed router" (let [router (reitit/router ["/api" ["/ipa" ["/:size" ::beer]]])] - (is (= :linear-router (reitit/router-type router))) + (is (= :mixed-router (reitit/router-type router))) (is (= [["/api/ipa/:size" {:name ::beer}]] (reitit/routes router))) (is (= true (map? (reitit/options router)))) @@ -106,6 +106,13 @@ (is handler) (is (= "ok" (handler))))))) + (testing "custom router" + (let [router (reitit/router ["/ping"] {:router (fn [_ _] + (reify Router + (reitit/router-type [_] + ::custom)))})] + (is (= ::custom (reitit/router-type router))))) + (testing "bide sample" (let [routes [["/auth/login" :auth/login] ["/auth/recovery/token/:token" :auth/recovery] @@ -175,10 +182,10 @@ (testing "all conflicts are returned" (is (= {["/a" {}] #{["/*d" {}] ["/:b" {}]}, ["/:b" {}] #{["/c" {}] ["/*d" {}]}, - ["/c" {}] #{["/*d" {}]}})) - (-> [["/a"] ["/:b"] ["/c"] ["/*d"]] - (reitit/resolve-routes {}) - (reitit/conflicting-routes))) + ["/c" {}] #{["/*d" {}]}} + (-> [["/a"] ["/:b"] ["/c"] ["/*d"]] + (reitit/resolve-routes {}) + (reitit/conflicting-routes))))) (testing "router with conflicting routes" (testing "throws by default" @@ -188,8 +195,4 @@ (reitit/router [["/a"] ["/a"]])))) (testing "can be configured to ignore" - (is (not - (nil? - (reitit/router - [["/a"] ["/a"]] - {:conflicts (constantly nil)}))))))) + (is (not (nil? (reitit/router [["/a"] ["/a"]] {:conflicts (constantly nil)})))))))