mirror of
https://github.com/metosin/reitit.git
synced 2025-12-18 08:51:12 +00:00
commit
95b796e94c
9 changed files with 568 additions and 130 deletions
|
|
@ -1,18 +1,25 @@
|
||||||
# Different Routers
|
# Different Routers
|
||||||
|
|
||||||
Reitit ships with several different implementations for the `Router` protocol, originally based on the [Pedestal](https://github.com/pedestal/pedestal/tree/master/route) implementation. `router` selects the most suitable implementation by inspecting the expanded routes. The implementation can be set manually using `:router` option, see [configuring routers](advanced/configuring_routers.md).
|
Reitit ships with several different implementations for the `Router` protocol, originally based on the [Pedestal](https://github.com/pedestal/pedestal/tree/master/route) implementation. `router` function selects the most suitable implementation by inspecting the expanded routes. The implementation can be set manually using `:router` option, see [configuring routers](advanced/configuring_routers.md).
|
||||||
|
|
||||||
| router | description |
|
| router | description |
|
||||||
| ------------------------------|-------------|
|
| ------------------------------|-------------|
|
||||||
| `:linear-router` | Matches the routes one-by-one starting from the top until a match is found. Works with any kind of routes.
|
| `:linear-router` | Matches the routes one-by-one starting from the top until a match is found. Works with any kind of routes. Slow, but works with all route trees.
|
||||||
| `:lookup-router` | Fast router, uses hash-lookup to resolve the route. Valid if no paths have path or catch-all parameters.
|
| `: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).
|
||||||
| `:mixed-router` | Creates internally a `:linear-router` and a `:lookup-router` and used them to effectively get best-of-both-worlds. Valid if there are no [Route conflicts](../basics/route_conflicts.md).
|
| `:mixed-router` | Creates internally a `:prefix-tree-router` and a `:lookup-router` and used them to effectively get best-of-both-worlds. Valid only if there are no [Route conflicts](../basics/route_conflicts.md).
|
||||||
| `::single-static-path-router` | Fastest possible router: valid only if there is one static route.
|
| `::single-static-path-router` | Super fast router: sting-matches the route. Valid only if there is one static route.
|
||||||
| `:prefix-tree-router` | TODO: https://github.com/julienschmidt/httprouter#how-does-it-work
|
| `:prefix-tree-router` | Router that creates a [prefix-tree](https://en.wikipedia.org/wiki/Radix_tree) out of an route table. Much faster than `:linear-router`. Valid only if there are no [Route conflicts](../basics/route_conflicts.md).
|
||||||
|
|
||||||
The router name can be asked from the router
|
The router name can be asked from the router:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
|
(require '[reitit.core :as r])
|
||||||
|
|
||||||
|
(def router
|
||||||
|
(r/router
|
||||||
|
[["/ping" ::ping]
|
||||||
|
["/api/:users" ::users]]))
|
||||||
|
|
||||||
(r/router-name router)
|
(r/router-name router)
|
||||||
; :mixed-router
|
; :mixed-router
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Namespace `reitit.spec` contains [clojure.spec](https://clojure.org/about/spec)
|
||||||
**NOTE:** Use of specs requires to use one of the following:
|
**NOTE:** Use of specs requires to use one of the following:
|
||||||
|
|
||||||
* `[org.clojure/clojurescript "1.9.660"]` (or higher)
|
* `[org.clojure/clojurescript "1.9.660"]` (or higher)
|
||||||
* `[org.clojure/clojure "1.9.0-beta2"]` (or higher)
|
* `[org.clojure/clojure "1.9.0-RC1"]` (or higher)
|
||||||
* `[clojure-future-spec "1.9.0-alpha17"]` (if Clojure 1.8 is used)
|
* `[clojure-future-spec "1.9.0-alpha17"]` (if Clojure 1.8 is used)
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
(ns reitit.core
|
(ns reitit.core
|
||||||
(:require [meta-merge.core :refer [meta-merge]]
|
(:require [meta-merge.core :refer [meta-merge]]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
|
[reitit.trie :as trie]
|
||||||
[reitit.impl :as impl #?@(:cljs [:refer [Route]])])
|
[reitit.impl :as impl #?@(:cljs [:refer [Route]])])
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(:import (reitit.impl Route))))
|
(:import (reitit.impl Route))))
|
||||||
|
|
@ -216,6 +217,46 @@
|
||||||
(if-let [match (impl/fast-get lookup name)]
|
(if-let [match (impl/fast-get lookup name)]
|
||||||
(match params)))))))
|
(match params)))))))
|
||||||
|
|
||||||
|
(defn prefix-tree-router
|
||||||
|
"Creates a prefix-tree router from resolved routes and optional
|
||||||
|
expanded options. See [[router]] for available options"
|
||||||
|
([routes]
|
||||||
|
(prefix-tree-router routes {}))
|
||||||
|
([routes opts]
|
||||||
|
(let [compiled (compile-routes routes opts)
|
||||||
|
names (find-names routes opts)
|
||||||
|
[node lookup] (reduce
|
||||||
|
(fn [[node lookup] [p {:keys [name] :as meta} result]]
|
||||||
|
(let [{:keys [params] :as route} (impl/create [p meta result])
|
||||||
|
f #(if-let [path (impl/path-for route %)]
|
||||||
|
(->Match p meta result % path)
|
||||||
|
(->PartialMatch p meta result % params))]
|
||||||
|
[(trie/insert node p (->Match p meta result nil nil))
|
||||||
|
(if name (assoc lookup name f) lookup)]))
|
||||||
|
[nil {}] compiled)
|
||||||
|
lookup (impl/fast-map lookup)]
|
||||||
|
(reify
|
||||||
|
Router
|
||||||
|
(router-name [_]
|
||||||
|
:prefix-tree-router)
|
||||||
|
(routes [_]
|
||||||
|
compiled)
|
||||||
|
(options [_]
|
||||||
|
opts)
|
||||||
|
(route-names [_]
|
||||||
|
names)
|
||||||
|
(match-by-path [_ path]
|
||||||
|
(if-let [match (trie/lookup node path {})]
|
||||||
|
(-> (:data match)
|
||||||
|
(assoc :params (:params match))
|
||||||
|
(assoc :path path))))
|
||||||
|
(match-by-name [_ name]
|
||||||
|
(if-let [match (impl/fast-get lookup name)]
|
||||||
|
(match nil)))
|
||||||
|
(match-by-name [_ name params]
|
||||||
|
(if-let [match (impl/fast-get lookup name)]
|
||||||
|
(match params)))))))
|
||||||
|
|
||||||
(defn single-static-path-router
|
(defn single-static-path-router
|
||||||
"Creates a fast router of 1 static route(s) and optional
|
"Creates a fast router of 1 static route(s) and optional
|
||||||
expanded options. See [[router]] for available options"
|
expanded options. See [[router]] for available options"
|
||||||
|
|
@ -252,16 +293,16 @@
|
||||||
|
|
||||||
(defn mixed-router
|
(defn mixed-router
|
||||||
"Creates two routers: [[lookup-router]] or [[single-static-path-router]] for
|
"Creates two routers: [[lookup-router]] or [[single-static-path-router]] for
|
||||||
static routes and [[linear-router]] for wildcard routes. All
|
static routes and [[prefix-tree-router]] for wildcard routes. All
|
||||||
routes should be non-conflicting. Takes resolved routes and optional
|
routes should be non-conflicting. Takes resolved routes and optional
|
||||||
expanded options. See [[router]] for options."
|
expanded options. See [[router]] for options."
|
||||||
([routes]
|
([routes]
|
||||||
(mixed-router routes {}))
|
(mixed-router routes {}))
|
||||||
([routes opts]
|
([routes opts]
|
||||||
(let [{linear true, lookup false} (group-by impl/wild-route? routes)
|
(let [{wild true, lookup false} (group-by impl/wild-route? routes)
|
||||||
compiled (compile-routes routes opts)
|
compiled (compile-routes routes opts)
|
||||||
->static-router (if (= 1 (count lookup)) single-static-path-router lookup-router)
|
->static-router (if (= 1 (count lookup)) single-static-path-router lookup-router)
|
||||||
wildcard-router (linear-router linear opts)
|
wildcard-router (prefix-tree-router wild opts)
|
||||||
static-router (->static-router lookup opts)
|
static-router (->static-router lookup opts)
|
||||||
names (find-names routes opts)]
|
names (find-names routes opts)]
|
||||||
(reify Router
|
(reify Router
|
||||||
|
|
@ -309,10 +350,10 @@
|
||||||
router (cond
|
router (cond
|
||||||
router router
|
router router
|
||||||
(and (= 1 (count routes)) (not wilds?)) single-static-path-router
|
(and (= 1 (count routes)) (not wilds?)) single-static-path-router
|
||||||
|
conflicting linear-router
|
||||||
(not wilds?) lookup-router
|
(not wilds?) lookup-router
|
||||||
all-wilds? linear-router
|
all-wilds? prefix-tree-router
|
||||||
(not conflicting) mixed-router
|
:else mixed-router)]
|
||||||
:else linear-router)]
|
|
||||||
|
|
||||||
(when-let [conflicts (:conflicts opts)]
|
(when-let [conflicts (:conflicts opts)]
|
||||||
(when conflicting (conflicts conflicting)))
|
(when conflicting (conflicts conflicting)))
|
||||||
|
|
|
||||||
|
|
@ -69,10 +69,20 @@
|
||||||
;; (c) https://github.com/pedestal/pedestal/blob/master/route/src/io/pedestal/http/route/prefix_tree.clj
|
;; (c) https://github.com/pedestal/pedestal/blob/master/route/src/io/pedestal/http/route/prefix_tree.clj
|
||||||
;;
|
;;
|
||||||
|
|
||||||
(defn- wild? [s]
|
(defn wild? [s]
|
||||||
(contains? #{\: \*} (first s)))
|
(contains? #{\: \*} (first s)))
|
||||||
|
|
||||||
(defn- partition-wilds
|
(defn wild-param?
|
||||||
|
"Return true if a string segment starts with a wildcard string."
|
||||||
|
[segment]
|
||||||
|
(= \: (first segment)))
|
||||||
|
|
||||||
|
(defn catch-all-param?
|
||||||
|
"Return true if a string segment starts with a catch-all string."
|
||||||
|
[segment]
|
||||||
|
(= \* (first segment)))
|
||||||
|
|
||||||
|
(defn partition-wilds
|
||||||
"Given a path-spec string, return a seq of strings with wildcards
|
"Given a path-spec string, return a seq of strings with wildcards
|
||||||
and catch-alls separated into their own strings. Eats the forward
|
and catch-alls separated into their own strings. Eats the forward
|
||||||
slash following a wildcard."
|
slash following a wildcard."
|
||||||
|
|
@ -161,7 +171,7 @@
|
||||||
:cljs [[a k v] (assoc a k v)]))
|
:cljs [[a k v] (assoc a k v)]))
|
||||||
|
|
||||||
(defn fast-map [m]
|
(defn fast-map [m]
|
||||||
#?@(:clj [(java.util.HashMap. m)]
|
#?@(:clj [(java.util.HashMap. (or m {}))]
|
||||||
:cljs [m]))
|
:cljs [m]))
|
||||||
|
|
||||||
(defn fast-get
|
(defn fast-get
|
||||||
|
|
|
||||||
223
modules/reitit-core/src/reitit/trie.cljc
Normal file
223
modules/reitit-core/src/reitit/trie.cljc
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
(ns reitit.trie
|
||||||
|
(:require [reitit.impl :as impl]))
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; original https://github.com/pedestal/pedestal/blob/master/route/src/io/pedestal/http/route/prefix_tree.clj
|
||||||
|
;;
|
||||||
|
|
||||||
|
(declare insert)
|
||||||
|
|
||||||
|
(defn- char-key [s i]
|
||||||
|
(if (< i (count s))
|
||||||
|
(subs s i (inc i))))
|
||||||
|
|
||||||
|
(defn- maybe-wild-node [children]
|
||||||
|
(get children ":"))
|
||||||
|
|
||||||
|
(defn- maybe-catch-all-node [children]
|
||||||
|
(get children "*"))
|
||||||
|
|
||||||
|
(defprotocol Node
|
||||||
|
(lookup [this path params])
|
||||||
|
(get-segment [this])
|
||||||
|
(update-segment [this subs lcs])
|
||||||
|
(get-data [this])
|
||||||
|
(set-data [this data])
|
||||||
|
(get-chidren [this])
|
||||||
|
(add-child [this key child])
|
||||||
|
(insert-child [this key path-spec data]))
|
||||||
|
|
||||||
|
(extend-protocol Node
|
||||||
|
nil
|
||||||
|
(lookup [_ _ _])
|
||||||
|
(get-segment [_]))
|
||||||
|
|
||||||
|
(defrecord Match [data params])
|
||||||
|
|
||||||
|
(defn- wild-node [segment param children data]
|
||||||
|
(let [?wild (maybe-wild-node children)
|
||||||
|
?catch (maybe-catch-all-node children)
|
||||||
|
children' (impl/fast-map children)]
|
||||||
|
^{:type ::node}
|
||||||
|
(reify
|
||||||
|
Node
|
||||||
|
(lookup [_ path params]
|
||||||
|
(let [i (.indexOf ^String path "/")]
|
||||||
|
(if (pos? i)
|
||||||
|
(let [value (subs path 0 i)]
|
||||||
|
(let [child (impl/fast-get children' (char-key path (inc i)))
|
||||||
|
path' (subs path (inc i))
|
||||||
|
params (assoc params param value)]
|
||||||
|
(or (lookup child path' params)
|
||||||
|
(lookup ?wild path' params)
|
||||||
|
(lookup ?catch path' params))))
|
||||||
|
(->Match data (assoc params param path)))))
|
||||||
|
(get-segment [_]
|
||||||
|
segment)
|
||||||
|
(get-data [_]
|
||||||
|
data)
|
||||||
|
(set-data [_ data]
|
||||||
|
(wild-node segment param children data))
|
||||||
|
(get-chidren [_]
|
||||||
|
children)
|
||||||
|
(add-child [_ key child]
|
||||||
|
(wild-node segment param (assoc children key child) data))
|
||||||
|
(insert-child [_ key path-spec child-data]
|
||||||
|
(wild-node segment param (update children key insert path-spec child-data) data)))))
|
||||||
|
|
||||||
|
(defn- catch-all-node [segment children param data]
|
||||||
|
^{:type ::node}
|
||||||
|
(reify
|
||||||
|
Node
|
||||||
|
(lookup [_ path params]
|
||||||
|
(->Match data (assoc params param path)))
|
||||||
|
(get-segment [_]
|
||||||
|
segment)
|
||||||
|
(get-data [_]
|
||||||
|
data)
|
||||||
|
(get-chidren [_]
|
||||||
|
children)))
|
||||||
|
|
||||||
|
(defn- static-node [^String segment children data]
|
||||||
|
(let [size (count segment)
|
||||||
|
?wild (maybe-wild-node children)
|
||||||
|
?catch (maybe-catch-all-node children)
|
||||||
|
children' (impl/fast-map children)]
|
||||||
|
^{:type ::node}
|
||||||
|
(reify
|
||||||
|
Node
|
||||||
|
(lookup [_ path params]
|
||||||
|
(if (#?(:clj .equals, :cljs =) segment path)
|
||||||
|
(->Match data params)
|
||||||
|
(let [p (if (>= (count path) size) (subs path 0 size))]
|
||||||
|
(if (#?(:clj .equals, :cljs =) segment p)
|
||||||
|
(let [child (impl/fast-get children' (char-key path size))
|
||||||
|
path (subs path size)]
|
||||||
|
(or (lookup child path params)
|
||||||
|
(lookup ?wild path params)
|
||||||
|
(lookup ?catch path params)))))))
|
||||||
|
(get-segment [_]
|
||||||
|
segment)
|
||||||
|
(update-segment [_ subs lcs]
|
||||||
|
(static-node (subs segment lcs) children data))
|
||||||
|
(get-data [_]
|
||||||
|
data)
|
||||||
|
(set-data [_ data]
|
||||||
|
(static-node segment children data))
|
||||||
|
(get-chidren [_]
|
||||||
|
children)
|
||||||
|
(add-child [_ key child]
|
||||||
|
(static-node segment (assoc children key child) data))
|
||||||
|
(insert-child [_ key path-spec child-data]
|
||||||
|
(static-node segment (update children key insert path-spec child-data) data)))))
|
||||||
|
|
||||||
|
(defn- make-node
|
||||||
|
"Given a path-spec segment string and a payload object, return a new
|
||||||
|
tree node."
|
||||||
|
[segment data]
|
||||||
|
(cond
|
||||||
|
(impl/wild-param? segment)
|
||||||
|
(wild-node segment (keyword (subs segment 1)) nil data)
|
||||||
|
|
||||||
|
(impl/catch-all-param? segment)
|
||||||
|
(catch-all-node segment (keyword (subs segment 1)) nil data)
|
||||||
|
|
||||||
|
:else
|
||||||
|
(static-node segment nil data)))
|
||||||
|
|
||||||
|
(defn- new-node
|
||||||
|
"Given a path-spec and a payload object, return a new tree node. If
|
||||||
|
the path-spec contains wildcards or catch-alls, will return parent
|
||||||
|
node of a tree (linked list)."
|
||||||
|
[path-spec data]
|
||||||
|
(if (impl/contains-wilds? path-spec)
|
||||||
|
(let [parts (impl/partition-wilds path-spec)]
|
||||||
|
(reduce (fn [child segment]
|
||||||
|
(when (impl/catch-all-param? segment)
|
||||||
|
(throw (ex-info "catch-all may only appear at the end of a path spec"
|
||||||
|
{:patch-spec path-spec})))
|
||||||
|
(-> (make-node segment nil)
|
||||||
|
(add-child (subs (get-segment child) 0 1) child)))
|
||||||
|
(let [segment (last parts)]
|
||||||
|
(make-node segment data))
|
||||||
|
(reverse (butlast parts))))
|
||||||
|
(make-node path-spec data)))
|
||||||
|
|
||||||
|
(defn- calc-lcs
|
||||||
|
"Given two strings, return the end index of the longest common
|
||||||
|
prefix string."
|
||||||
|
[s1 s2]
|
||||||
|
(loop [i 1]
|
||||||
|
(cond (or (< (count s1) i)
|
||||||
|
(< (count s2) i))
|
||||||
|
(dec i)
|
||||||
|
|
||||||
|
(= (subs s1 0 i)
|
||||||
|
(subs s2 0 i))
|
||||||
|
(recur (inc i))
|
||||||
|
|
||||||
|
:else (dec i))))
|
||||||
|
|
||||||
|
(defn- split
|
||||||
|
"Given a node, a path-spec, a payload object to insert into the tree
|
||||||
|
and the lcs, split the node and return a new parent node with the
|
||||||
|
old contents of node and the new item as children.
|
||||||
|
lcs is the index of the longest common string in path-spec and the
|
||||||
|
segment of node."
|
||||||
|
[node path-spec data lcs]
|
||||||
|
(let [segment (get-segment node)
|
||||||
|
common (subs path-spec 0 lcs)
|
||||||
|
parent (new-node common nil)]
|
||||||
|
(if (= common path-spec)
|
||||||
|
(-> (set-data parent data)
|
||||||
|
(add-child (char-key segment lcs) (update-segment node subs lcs)))
|
||||||
|
(-> parent
|
||||||
|
(add-child (char-key segment lcs) (update-segment node subs lcs))
|
||||||
|
(insert-child (char-key path-spec lcs) (subs path-spec lcs) data)))))
|
||||||
|
|
||||||
|
(defn insert
|
||||||
|
"Given a tree node, a path-spec and a payload object, return a new
|
||||||
|
tree with payload inserted."
|
||||||
|
[node path-spec data]
|
||||||
|
(let [segment (get-segment node)]
|
||||||
|
(cond (nil? node)
|
||||||
|
(new-node path-spec data)
|
||||||
|
|
||||||
|
(= segment path-spec)
|
||||||
|
(set-data node data)
|
||||||
|
|
||||||
|
;; handle case where path-spec is a wildcard param
|
||||||
|
(impl/wild-param? path-spec)
|
||||||
|
(let [lcs (calc-lcs segment path-spec)
|
||||||
|
common (subs path-spec 0 lcs)]
|
||||||
|
(if (= common segment)
|
||||||
|
(let [path-spec (subs path-spec (inc lcs))]
|
||||||
|
(insert-child node (subs path-spec 0 1) path-spec data))
|
||||||
|
(throw (ex-info "route conflict"
|
||||||
|
{:node node
|
||||||
|
:path-spec path-spec
|
||||||
|
:segment segment}))))
|
||||||
|
|
||||||
|
;; in the case where path-spec is a catch-all, node should always be nil.
|
||||||
|
;; getting here means we have an invalid route specification
|
||||||
|
(impl/catch-all-param? path-spec)
|
||||||
|
(throw (ex-info "route conflict"
|
||||||
|
{:node node
|
||||||
|
:path-spec path-spec
|
||||||
|
:segment segment}))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(let [lcs (calc-lcs segment path-spec)]
|
||||||
|
(cond (= lcs (count segment))
|
||||||
|
(insert-child node (char-key path-spec lcs) (subs path-spec lcs) data)
|
||||||
|
|
||||||
|
:else
|
||||||
|
(split node path-spec data lcs))))))
|
||||||
|
|
||||||
|
(defn view
|
||||||
|
"Returns a view representation of a prefix-tree."
|
||||||
|
[x]
|
||||||
|
(vec (concat
|
||||||
|
[(get-segment x)]
|
||||||
|
(some->> (get-chidren x) vals seq (map view))
|
||||||
|
(some->> (get-data x) vector))))
|
||||||
|
|
@ -5,13 +5,14 @@
|
||||||
[cheshire.core :as json]
|
[cheshire.core :as json]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[reitit.core :as reitit]
|
[reitit.core :as reitit]
|
||||||
[reitit.core :as ring]
|
[reitit.ring :as ring]
|
||||||
|
|
||||||
[bidi.bidi :as bidi]
|
[bidi.bidi :as bidi]
|
||||||
|
|
||||||
[ataraxy.core :as ataraxy]
|
[ataraxy.core :as ataraxy]
|
||||||
|
|
||||||
[compojure.api.sweet :refer [api routes context ANY]]
|
[compojure.api.sweet :refer [api routes context ANY]]
|
||||||
|
[compojure.core :as compojure]
|
||||||
|
|
||||||
[io.pedestal.http.route.definition.table :as table]
|
[io.pedestal.http.route.definition.table :as table]
|
||||||
[io.pedestal.http.route.map-tree :as map-tree]
|
[io.pedestal.http.route.map-tree :as map-tree]
|
||||||
|
|
@ -72,19 +73,11 @@
|
||||||
total 10000
|
total 10000
|
||||||
dropped (int (* total 0.45))]
|
dropped (int (* total 0.45))]
|
||||||
(mapv
|
(mapv
|
||||||
#(let [times (->> (range total)
|
(fn [path]
|
||||||
(mapv
|
(let [time (int (* (first (:sample-mean (cc/quick-benchmark (dotimes [_ 1000] (f path)) {}))) 1e6))]
|
||||||
(fn [_]
|
(println path "=>" time "ns")
|
||||||
(let [now (System/nanoTime)
|
[path time]))
|
||||||
result (f %)
|
urls)))
|
||||||
total (- (System/nanoTime) now)]
|
|
||||||
(assert result)
|
|
||||||
total)))
|
|
||||||
(sort)
|
|
||||||
(drop dropped)
|
|
||||||
(drop-last dropped))
|
|
||||||
avg (int (/ (reduce + times) (count times)))]
|
|
||||||
[% avg]) urls)))
|
|
||||||
|
|
||||||
(defn bench [routes no-paths?]
|
(defn bench [routes no-paths?]
|
||||||
(let [routes (mapv (fn [[path name]]
|
(let [routes (mapv (fn [[path name]]
|
||||||
|
|
@ -278,6 +271,82 @@
|
||||||
["topics/" topic] [:test/route47 topic]
|
["topics/" topic] [:test/route47 topic]
|
||||||
"topics" [:test/route50]}}))
|
"topics" [:test/route50]}}))
|
||||||
|
|
||||||
|
(def opensensors-compojure-routes
|
||||||
|
(compojure/routes
|
||||||
|
(compojure/context "/v1" []
|
||||||
|
(compojure/context "/public" []
|
||||||
|
(compojure/ANY "/topics/:topic" [] {:name :test/route4} handler)
|
||||||
|
(compojure/ANY "/users/:user-id" [] {:name :test/route16} handler)
|
||||||
|
(compojure/ANY "/orgs/:org-id" [] {:name :test/route18} handler))
|
||||||
|
(compojure/context "/users/:user-id" []
|
||||||
|
(compojure/ANY "/orgs/:org-id" [] {:name :test/route5} handler)
|
||||||
|
(compojure/ANY "/invitations" [] {:name :test/route7} handler)
|
||||||
|
(compojure/ANY "/topics" [] {:name :test/route9} handler)
|
||||||
|
(compojure/ANY "/bookmarks/followers" [] {:name :test/route10} handler)
|
||||||
|
(compojure/context "/devices" []
|
||||||
|
(compojure/ANY "/" [] {:name :test/route15} handler)
|
||||||
|
#_(compojure/ANY "/bulk" [] {:name :test/route21} handler)
|
||||||
|
(compojure/ANY "/:client-id" [] {:name :test/route35} handler)
|
||||||
|
(compojure/ANY "/:client-id/reset-password" [] {:name :test/route49} handler))
|
||||||
|
(compojure/ANY "/device-errors" [] {:name :test/route22} handler)
|
||||||
|
(compojure/ANY "/usage-stats" [] {:name :test/route24} handler)
|
||||||
|
(compojure/ANY "/claim-device/:client-id" [] {:name :test/route26} handler)
|
||||||
|
(compojure/ANY "/owned-orgs" [] {:name :test/route31} handler)
|
||||||
|
(compojure/ANY "/bookmark/:topic" [] {:name :test/route33} handler)
|
||||||
|
(compojure/ANY "/" [] {:name :test/route36} handler)
|
||||||
|
(compojure/ANY "/orgs" [] {:name :test/route52} handler)
|
||||||
|
(compojure/ANY "/api-key" [] {:name :test/route43} handler)
|
||||||
|
(compojure/ANY "/bookmarks" [] {:name :test/route56} handler))
|
||||||
|
(compojure/ANY "/search/topics/:term" [] {:name :test/route6} handler)
|
||||||
|
(compojure/context "/orgs" []
|
||||||
|
(compojure/ANY "/" [] {:name :test/route55} handler)
|
||||||
|
(compojure/context "/:org-id" []
|
||||||
|
(compojure/context "/devices" []
|
||||||
|
(compojure/ANY "/" [] {:name :test/route37} handler)
|
||||||
|
(compojure/ANY "/:device-id" [] {:name :test/route13} handler)
|
||||||
|
#_(compojure/ANY "/:batch/:type" [] {:name :test/route8} handler))
|
||||||
|
(compojure/ANY "/usage-stats" [] {:name :test/route12} handler)
|
||||||
|
(compojure/ANY "/invitations" [] {:name :test/route19} handler)
|
||||||
|
(compojure/context "/members" []
|
||||||
|
(compojure/ANY "/:user-id" [] {:name :test/route34} handler)
|
||||||
|
(compojure/ANY "/" [] {:name :test/route38} handler)
|
||||||
|
#_(compojure/ANY "/invitation-data/:user-id" [] {:name :test/route39} handler))
|
||||||
|
(compojure/ANY "/errors" [] {:name :test/route17} handler)
|
||||||
|
(compojure/ANY "/" [] {:name :test/route42} handler)
|
||||||
|
(compojure/ANY "/confirm-membership/:token" [] {:name :test/route46} handler)
|
||||||
|
(compojure/ANY "/topics" [] {:name :test/route57} handler)))
|
||||||
|
(compojure/context "/messages" []
|
||||||
|
(compojure/ANY "/user/:user-id" [] {:name :test/route14} handler)
|
||||||
|
(compojure/ANY "/device/:client-id" [] {:name :test/route30} handler)
|
||||||
|
(compojure/ANY "/topic/:topic" [] {:name :test/route48} handler))
|
||||||
|
(compojure/context "/topics" []
|
||||||
|
(compojure/ANY "/:topic" [] {:name :test/route32} handler)
|
||||||
|
(compojure/ANY "/" [] {:name :test/route54} handler))
|
||||||
|
(compojure/ANY "/whoami" [] {:name :test/route41} handler)
|
||||||
|
(compojure/ANY "/login" [] {:name :test/route51} handler))
|
||||||
|
(compojure/context "/v2" []
|
||||||
|
(compojure/ANY "/whoami" [] {:name :test/route1} handler)
|
||||||
|
(compojure/context "/users/:user-id" []
|
||||||
|
(compojure/ANY "/datasets" [] {:name :test/route2} handler)
|
||||||
|
(compojure/ANY "/devices" [] {:name :test/route25} handler)
|
||||||
|
(compojure/context "/topics" []
|
||||||
|
(compojure/ANY "/bulk" [] {:name :test/route29} handler)
|
||||||
|
(compojure/ANY "/" [] {:name :test/route54} handler))
|
||||||
|
(compojure/ANY "/" [] {:name :test/route45} handler))
|
||||||
|
(compojure/context "/public" []
|
||||||
|
(compojure/context "/projects/:project-id" []
|
||||||
|
(compojure/ANY "/datasets" [] {:name :test/route3} handler)
|
||||||
|
(compojure/ANY "/" [] {:name :test/route27} handler))
|
||||||
|
#_(compojure/ANY "/messages/dataset/bulk" [] {:name :test/route20} handler)
|
||||||
|
(compojure/ANY "/datasets/:dataset-id" [] {:name :test/route28} handler)
|
||||||
|
(compojure/ANY "/messages/dataset/:dataset-id" [] {:name :test/route53} handler))
|
||||||
|
(compojure/ANY "/datasets/:dataset-id" [] {:name :test/route11} handler)
|
||||||
|
(compojure/ANY "/login" [] {:name :test/route23} handler)
|
||||||
|
(compojure/ANY "/orgs/:org-id/topics" [] {:name :test/route40} handler)
|
||||||
|
(compojure/ANY "/schemas" [] {:name :test/route44} handler)
|
||||||
|
(compojure/ANY "/topics/:topic" [] {:name :test/route47} handler)
|
||||||
|
(compojure/ANY "/topics" [] {:name :test/route50} handler))))
|
||||||
|
|
||||||
(def opensensors-compojure-api-routes
|
(def opensensors-compojure-api-routes
|
||||||
(routes
|
(routes
|
||||||
(context "/v1" []
|
(context "/v1" []
|
||||||
|
|
@ -490,25 +559,33 @@
|
||||||
#(app {:uri % :request-method :get}))
|
#(app {:uri % :request-method :get}))
|
||||||
bidi-f #(bidi/match-route opensensors-bidi-routes %)
|
bidi-f #(bidi/match-route opensensors-bidi-routes %)
|
||||||
ataraxy-f #(ataraxy/matches opensensors-ataraxy-routes {:uri %})
|
ataraxy-f #(ataraxy/matches opensensors-ataraxy-routes {:uri %})
|
||||||
|
compojure-f #(opensensors-compojure-routes {:uri % :request-method :get})
|
||||||
compojure-api-f #(opensensors-compojure-api-routes {:uri % :request-method :get})
|
compojure-api-f #(opensensors-compojure-api-routes {:uri % :request-method :get})
|
||||||
pedestal-f #(pedestal/find-route opensensors-pedestal-routes {:path-info % :request-method :get})]
|
pedestal-f #(pedestal/find-route opensensors-pedestal-routes {:path-info % :request-method :get})]
|
||||||
|
|
||||||
;; 2538ns -> 2028ns
|
;; 2538ns
|
||||||
|
;; 2065ns
|
||||||
|
;; 680ns (prefix-tree-router)
|
||||||
(bench!! routes true "reitit" reitit-f)
|
(bench!! routes true "reitit" reitit-f)
|
||||||
|
|
||||||
;; 2845ns -> 2299ns
|
;; 2845ns
|
||||||
|
;; 2316ns
|
||||||
|
;; 947ns (prefix-tree-router)
|
||||||
(bench!! routes true "reitit-ring" reitit-ring-f)
|
(bench!! routes true "reitit-ring" reitit-ring-f)
|
||||||
|
|
||||||
;; 2737ns
|
;; 2541ns
|
||||||
(bench!! routes true "pedestal" pedestal-f)
|
(bench!! routes true "pedestal" pedestal-f)
|
||||||
|
|
||||||
;; 9823ns
|
;; 9462ns
|
||||||
(bench!! routes true "compojure-api" compojure-api-f)
|
(bench!! routes true "compojure-api" compojure-api-f)
|
||||||
|
|
||||||
;; 16716ns
|
;; 11041ns
|
||||||
|
(bench!! routes true "compojure" compojure-f)
|
||||||
|
|
||||||
|
;; 16820ns
|
||||||
(bench!! routes true "bidi" bidi-f)
|
(bench!! routes true "bidi" bidi-f)
|
||||||
|
|
||||||
;; 24467ns
|
;; 24134ns
|
||||||
(bench!! routes true "ataraxy" ataraxy-f)))
|
(bench!! routes true "ataraxy" ataraxy-f)))
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@
|
||||||
(call))))
|
(call))))
|
||||||
|
|
||||||
;; 710 µs (3-18x)
|
;; 710 µs (3-18x)
|
||||||
|
;; 540 µs (4-23x) -23% prefix-tree-router
|
||||||
(title "reitit")
|
(title "reitit")
|
||||||
(let [call #(reitit/match-by-path reitit-routes "/workspace/1/1")]
|
(let [call #(reitit/match-by-path reitit-routes "/workspace/1/1")]
|
||||||
(assert (call))
|
(assert (call))
|
||||||
|
|
|
||||||
105
perf-test/clj/reitit/prefix_tree_perf_test.clj
Normal file
105
perf-test/clj/reitit/prefix_tree_perf_test.clj
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
(ns reitit.prefix-tree-perf-test
|
||||||
|
(:require [clojure.test :refer :all]
|
||||||
|
[io.pedestal.http.route.prefix-tree :as p]
|
||||||
|
[reitit.trie :as trie]
|
||||||
|
[criterium.core :as cc]))
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; testing
|
||||||
|
;;
|
||||||
|
|
||||||
|
(def routes
|
||||||
|
[["/v2/whoami" {:name :test/route1}]
|
||||||
|
["/v2/users/:user-id/datasets" {:name :test/route2}]
|
||||||
|
["/v2/public/projects/:project-id/datasets" {:name :test/route3}]
|
||||||
|
["/v1/public/topics/:topic" {:name :test/route4}]
|
||||||
|
["/v1/users/:user-id/orgs/:org-id" {:name :test/route5}]
|
||||||
|
["/v1/search/topics/:term" {:name :test/route6}]
|
||||||
|
["/v1/users/:user-id/invitations" {:name :test/route7}]
|
||||||
|
["/v1/users/:user-id/topics" {:name :test/route9}]
|
||||||
|
["/v1/users/:user-id/bookmarks/followers" {:name :test/route10}]
|
||||||
|
["/v2/datasets/:dataset-id" {:name :test/route11}]
|
||||||
|
["/v1/orgs/:org-id/usage-stats" {:name :test/route12}]
|
||||||
|
["/v1/orgs/:org-id/devices/:client-id" {:name :test/route13}]
|
||||||
|
["/v1/messages/user/:user-id" {:name :test/route14}]
|
||||||
|
["/v1/users/:user-id/devices" {:name :test/route15}]
|
||||||
|
["/v1/public/users/:user-id" {:name :test/route16}]
|
||||||
|
["/v1/orgs/:org-id/errors" {:name :test/route17}]
|
||||||
|
["/v1/public/orgs/:org-id" {:name :test/route18}]
|
||||||
|
["/v1/orgs/:org-id/invitations" {:name :test/route19}]
|
||||||
|
["/v1/users/:user-id/device-errors" {:name :test/route22}]
|
||||||
|
["/v2/login" {:name :test/route23}]
|
||||||
|
["/v1/users/:user-id/usage-stats" {:name :test/route24}]
|
||||||
|
["/v2/users/:user-id/devices" {:name :test/route25}]
|
||||||
|
["/v1/users/:user-id/claim-device/:client-id" {:name :test/route26}]
|
||||||
|
["/v2/public/projects/:project-id" {:name :test/route27}]
|
||||||
|
["/v2/public/datasets/:dataset-id" {:name :test/route28}]
|
||||||
|
["/v2/users/:user-id/topics/bulk" {:name :test/route29}]
|
||||||
|
["/v1/messages/device/:client-id" {:name :test/route30}]
|
||||||
|
["/v1/users/:user-id/owned-orgs" {:name :test/route31}]
|
||||||
|
["/v1/topics/:topic" {:name :test/route32}]
|
||||||
|
["/v1/users/:user-id/bookmark/:topic" {:name :test/route33}]
|
||||||
|
["/v1/orgs/:org-id/members/:user-id" {:name :test/route34}]
|
||||||
|
["/v1/users/:user-id/devices/:client-id" {:name :test/route35}]
|
||||||
|
["/v1/users/:user-id" {:name :test/route36}]
|
||||||
|
["/v1/orgs/:org-id/devices" {:name :test/route37}]
|
||||||
|
["/v1/orgs/:org-id/members" {:name :test/route38}]
|
||||||
|
["/v2/orgs/:org-id/topics" {:name :test/route40}]
|
||||||
|
["/v1/whoami" {:name :test/route41}]
|
||||||
|
["/v1/orgs/:org-id" {:name :test/route42}]
|
||||||
|
["/v1/users/:user-id/api-key" {:name :test/route43}]
|
||||||
|
["/v2/schemas" {:name :test/route44}]
|
||||||
|
["/v2/users/:user-id/topics" {:name :test/route45}]
|
||||||
|
["/v1/orgs/:org-id/confirm-membership/:token" {:name :test/route46}]
|
||||||
|
["/v2/topics/:topic" {:name :test/route47}]
|
||||||
|
["/v1/messages/topic/:topic" {:name :test/route48}]
|
||||||
|
["/v1/users/:user-id/devices/:client-id/reset-password" {:name :test/route49}]
|
||||||
|
["/v2/topics" {:name :test/route50}]
|
||||||
|
["/v1/login" {:name :test/route51}]
|
||||||
|
["/v1/users/:user-id/orgs" {:name :test/route52}]
|
||||||
|
["/v2/public/messages/dataset/:dataset-id" {:name :test/route53}]
|
||||||
|
["/v1/topics" {:name :test/route54}]
|
||||||
|
["/v1/orgs" {:name :test/route55}]
|
||||||
|
["/v1/users/:user-id/bookmarks" {:name :test/route56}]
|
||||||
|
["/v1/orgs/:org-id/topics" {:name :test/route57}]])
|
||||||
|
|
||||||
|
(def pedestal-tree
|
||||||
|
(reduce
|
||||||
|
(fn [acc [p d]]
|
||||||
|
(p/insert acc p d))
|
||||||
|
nil routes))
|
||||||
|
|
||||||
|
(def reitit-tree
|
||||||
|
(reduce
|
||||||
|
(fn [acc [p d]]
|
||||||
|
(trie/insert acc p d))
|
||||||
|
nil routes))
|
||||||
|
|
||||||
|
(defn bench! []
|
||||||
|
|
||||||
|
;; 2.3ms
|
||||||
|
(cc/quick-bench
|
||||||
|
(dotimes [_ 1000]
|
||||||
|
(p/lookup pedestal-tree "/v1/orgs/1/topics")))
|
||||||
|
|
||||||
|
;; 3.1ms
|
||||||
|
;; 2.5ms (string equals)
|
||||||
|
;; 2.5ms (protocol)
|
||||||
|
;; 2.3ms (nil childs)
|
||||||
|
;; 2.0ms (rando impros)
|
||||||
|
;; 1.9ms (wild & catch shortcuts)
|
||||||
|
;; 1.5ms (inline child fetching)
|
||||||
|
;; 1.5ms (WildNode also backtracks)
|
||||||
|
;; 1.4ms (precalculate segment-size)
|
||||||
|
;; 1.3ms (fast-map)
|
||||||
|
;; 1.3ms (dissoc wild & catch-all from children)
|
||||||
|
;; 1.3ms (reified protocols)
|
||||||
|
;; 0.8ms (flattened matching)
|
||||||
|
;; 0.8ms (return route-data)
|
||||||
|
;; 0.8ms (fix payloads)
|
||||||
|
(cc/quick-bench
|
||||||
|
(dotimes [_ 1000]
|
||||||
|
(trie/lookup reitit-tree "/v1/orgs/1/topics" {}))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(bench!))
|
||||||
|
|
@ -7,106 +7,80 @@
|
||||||
|
|
||||||
(deftest reitit-test
|
(deftest reitit-test
|
||||||
|
|
||||||
(testing "linear-router"
|
(testing "routers handling wildcard paths"
|
||||||
(let [router (r/router ["/api" ["/ipa" ["/:size" ::beer]]])]
|
(are [r name]
|
||||||
(is (= :linear-router (r/router-name router)))
|
(let [router (r/router ["/api" ["/ipa" ["/:size" ::beer]]] {:router r})]
|
||||||
(is (= [["/api/ipa/:size" {:name ::beer} nil]]
|
(is (= name (r/router-name router)))
|
||||||
(r/routes router)))
|
(is (= [["/api/ipa/:size" {:name ::beer} nil]]
|
||||||
(is (= true (map? (r/options router))))
|
(r/routes router)))
|
||||||
(is (= (r/map->Match
|
(is (= true (map? (r/options router))))
|
||||||
{:template "/api/ipa/:size"
|
(is (= (r/map->Match
|
||||||
:meta {:name ::beer}
|
|
||||||
:path "/api/ipa/large"
|
|
||||||
:params {:size "large"}})
|
|
||||||
(r/match-by-path router "/api/ipa/large")))
|
|
||||||
(is (= (r/map->Match
|
|
||||||
{:template "/api/ipa/:size"
|
|
||||||
: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 "name-based routing with missing parameters"
|
|
||||||
(is (= (r/map->PartialMatch
|
|
||||||
{:template "/api/ipa/:size"
|
{:template "/api/ipa/:size"
|
||||||
:meta {:name ::beer}
|
:meta {:name ::beer}
|
||||||
:required #{:size}
|
:path "/api/ipa/large"
|
||||||
:params nil})
|
:params {:size "large"}})
|
||||||
(r/match-by-name router ::beer)))
|
(r/match-by-path router "/api/ipa/large")))
|
||||||
(is (= true (r/partial-match? (r/match-by-name router ::beer))))
|
(is (= (r/map->Match
|
||||||
(is (thrown-with-msg?
|
{:template "/api/ipa/:size"
|
||||||
ExceptionInfo
|
:meta {:name ::beer}
|
||||||
#"^missing path-params for route /api/ipa/:size -> \#\{:size\}$"
|
:path "/api/ipa/large"
|
||||||
(r/match-by-name! router ::beer))))))
|
: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 "lookup-router"
|
(testing "name-based routing with missing parameters"
|
||||||
(let [router (r/router ["/api" ["/ipa" ["/large" ::beer]]] {:router r/lookup-router})]
|
(is (= (r/map->PartialMatch
|
||||||
(is (= :lookup-router (r/router-name router)))
|
{:template "/api/ipa/:size"
|
||||||
(is (= [["/api/ipa/large" {:name ::beer} nil]]
|
:meta {:name ::beer}
|
||||||
(r/routes router)))
|
:required #{:size}
|
||||||
(is (= true (map? (r/options router))))
|
:params nil})
|
||||||
(is (= (r/map->Match
|
(r/match-by-name router ::beer)))
|
||||||
{:template "/api/ipa/large"
|
(is (= true (r/partial-match? (r/match-by-name router ::beer))))
|
||||||
:meta {:name ::beer}
|
(is (thrown-with-msg?
|
||||||
:path "/api/ipa/large"
|
ExceptionInfo
|
||||||
:params {}})
|
#"^missing path-params for route /api/ipa/:size -> \#\{:size\}$"
|
||||||
(r/match-by-path router "/api/ipa/large")))
|
(r/match-by-name! router ::beer)))))
|
||||||
(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"
|
r/linear-router :linear-router
|
||||||
(is (thrown-with-msg?
|
r/prefix-tree-router :prefix-tree-router
|
||||||
ExceptionInfo
|
r/mixed-router :mixed-router))
|
||||||
#"can't create :lookup-router with wildcard routes"
|
|
||||||
(r/lookup-router
|
|
||||||
(r/resolve-routes
|
|
||||||
["/api/:version/ping"] {})))))))
|
|
||||||
|
|
||||||
(testing "single-static-path-router"
|
(testing "routers handling static paths"
|
||||||
(let [router (r/router ["/api" ["/ipa" ["/large" ::beer]]])]
|
(are [r name]
|
||||||
(is (= :single-static-path-router (r/router-name router)))
|
(let [router (r/router ["/api" ["/ipa" ["/large" ::beer]]] {:router r})]
|
||||||
(is (= [["/api/ipa/large" {:name ::beer} nil]]
|
(is (= name (r/router-name router)))
|
||||||
(r/routes router)))
|
(is (= [["/api/ipa/large" {:name ::beer} nil]]
|
||||||
(is (= true (map? (r/options router))))
|
(r/routes router)))
|
||||||
(is (= (r/map->Match
|
(is (= true (map? (r/options router))))
|
||||||
{:template "/api/ipa/large"
|
(is (= (r/map->Match
|
||||||
:meta {:name ::beer}
|
{:template "/api/ipa/large"
|
||||||
:path "/api/ipa/large"
|
:meta {:name ::beer}
|
||||||
:params {}})
|
:path "/api/ipa/large"
|
||||||
(r/match-by-path router "/api/ipa/large")))
|
:params {}})
|
||||||
(is (= (r/map->Match
|
(r/match-by-path router "/api/ipa/large")))
|
||||||
{:template "/api/ipa/large"
|
(is (= (r/map->Match
|
||||||
:meta {:name ::beer}
|
{:template "/api/ipa/large"
|
||||||
:path "/api/ipa/large"
|
:meta {:name ::beer}
|
||||||
:params {:size "large"}})
|
:path "/api/ipa/large"
|
||||||
(r/match-by-name router ::beer {:size "large"})))
|
:params {:size "large"}})
|
||||||
(is (= nil (r/match-by-name router "ILLEGAL")))
|
(r/match-by-name router ::beer {:size "large"})))
|
||||||
(is (= [::beer] (r/route-names router)))
|
(is (= nil (r/match-by-name router "ILLEGAL")))
|
||||||
|
(is (= [::beer] (r/route-names router)))
|
||||||
|
|
||||||
(testing "can't be created with wildcard routes"
|
(testing "can't be created with wildcard routes"
|
||||||
(is (thrown-with-msg?
|
(is (thrown-with-msg?
|
||||||
ExceptionInfo
|
ExceptionInfo
|
||||||
#":single-static-path-router requires exactly 1 static route"
|
#"can't create :lookup-router with wildcard routes"
|
||||||
(r/single-static-path-router
|
(r/lookup-router
|
||||||
(r/resolve-routes
|
(r/resolve-routes
|
||||||
["/api/:version/ping"] {})))))
|
["/api/:version/ping"] {}))))))
|
||||||
|
|
||||||
(testing "can't be created with multiple routes"
|
r/lookup-router :lookup-router
|
||||||
(is (thrown-with-msg?
|
r/single-static-path-router :single-static-path-router
|
||||||
ExceptionInfo
|
r/linear-router :linear-router
|
||||||
#":single-static-path-router requires exactly 1 static route"
|
r/prefix-tree-router :prefix-tree-router
|
||||||
(r/single-static-path-router
|
r/mixed-router :mixed-router))
|
||||||
(r/resolve-routes
|
|
||||||
[["/ping"]
|
|
||||||
["/pong"]] {})))))))
|
|
||||||
|
|
||||||
(testing "route coercion & compilation"
|
(testing "route coercion & compilation"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue