diff --git a/modules/reitit-core/java-src/reitit/Trie.java b/modules/reitit-core/java-src/reitit/Trie.java index b9b3b167..ac88937a 100644 --- a/modules/reitit-core/java-src/reitit/Trie.java +++ b/modules/reitit-core/java-src/reitit/Trie.java @@ -34,7 +34,6 @@ public class Trie { hasPlus = true; } } - System.err.println(); return decode(chars, offset, count, hasPercent, hasPlus); } diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index 307ba91e..7c2c8919 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -1,10 +1,11 @@ (ns reitit.core - (:require [meta-merge.core :refer [meta-merge]] - [clojure.string :as str] - [reitit.trie :as trie] - [reitit.impl :as impl #?@(:cljs [:refer [Route]])]) - #?(:clj - (:import (reitit.impl Route)))) + (:require [clojure.string :as str] + [reitit.impl :as impl] + [reitit.trie :as trie])) + +;; +;; Expand +;; (defprotocol Expand (expand [this opts])) @@ -30,56 +31,9 @@ nil (expand [_ _])) -(defn walk [raw-routes {:keys [path data routes expand] - :or {data [], routes [], expand expand} - :as opts}] - (letfn - [(walk-many [p m r] - (reduce #(into %1 (walk-one p m %2)) [] r)) - (walk-one [pacc macc routes] - (if (vector? (first routes)) - (walk-many pacc macc routes) - (when (string? (first routes)) - (let [[path & [maybe-arg :as args]] routes - [data childs] (if (or (vector? maybe-arg) - (and (sequential? maybe-arg) - (sequential? (first maybe-arg))) - (nil? maybe-arg)) - [{} args] - [maybe-arg (rest args)]) - macc (into macc (expand data opts)) - child-routes (walk-many (str pacc path) macc (keep identity childs))] - (if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))] - (walk-one path (mapv identity data) raw-routes))) - -(defn map-data [f routes] - (mapv #(update % 1 f) routes)) - -(defn merge-data [x] - (reduce - (fn [acc [k v]] - (meta-merge acc {k v})) - {} x)) - -(defn resolve-routes [raw-routes {:keys [coerce] :as opts}] - (cond->> (->> (walk raw-routes opts) (map-data merge-data)) - coerce (into [] (keep #(coerce % opts))))) - -(defn path-conflicting-routes [routes] - (-> (into {} - (comp (map-indexed (fn [index route] - [route (into #{} - (filter #(impl/conflicting-routes? route %)) - (subvec routes (inc index)))])) - (filter (comp seq second))) - routes) - (not-empty))) - -(defn conflicting-paths [conflicts] - (->> (for [[p pc] conflicts] - (conj (map first pc) (first p))) - (apply concat) - (set))) +;; +;; Conflicts +;; (defn path-conflicts-str [conflicts] (apply str "Router contains conflicting route paths:\n\n" @@ -88,15 +42,6 @@ (str " " path "\n-> " (str/join "\n-> " (mapv first vals)) "\n\n")) conflicts))) -(defn name-conflicting-routes [routes] - (some->> routes - (group-by (comp :name second)) - (remove (comp nil? first)) - (filter (comp pos? count butlast second)) - (seq) - (map (fn [[k v]] [k (set v)])) - (into {}))) - (defn name-conflicts-str [conflicts] (apply str "Router contains conflicting route names:\n\n" (mapv @@ -110,23 +55,9 @@ (f conflicts) {:conflicts conflicts}))) -(defn- name-lookup [[_ {:keys [name]}] _] - (if name #{name})) - -(defn- find-names [routes _] - (into [] (keep #(-> % second :name)) routes)) - -(defn- compile-route [[p m :as route] {:keys [compile] :as opts}] - [p m (if compile (compile route opts))]) - -(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] - (impl/create route)) +;; +;; Router +;; (defprotocol Router (router-name [this]) @@ -162,26 +93,30 @@ ([match query-params] (some-> match :path (cond-> query-params (str "?" (impl/query-string query-params)))))) +;; +;; Different routers +;; + (defn linear-router "Creates a linear-router from resolved routes and optional expanded options. See [[router]] for available options." ([compiled-routes] (linear-router compiled-routes {})) ([compiled-routes opts] - (let [names (find-names compiled-routes opts) + (let [names (impl/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]) + (let [{:keys [path-params] :as route} (impl/parse p) f #(if-let [path (impl/path-for route %)] (->Match p data result (impl/url-decode-coll %) path) - (->PartialMatch p data result % path-params))] + (->PartialMatch p data result (impl/url-decode-coll %) path-params))] [(conj pl (-> (trie/insert nil p (->Match p data result nil nil)) (trie/compile))) (if name (assoc nl name f) nl)])) [[] {}] compiled-routes) lookup (impl/fast-map nl) scanner (trie/scanner pl) - routes (uncompile-routes compiled-routes)] + routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router @@ -219,7 +154,7 @@ (str "can't create :lookup-router with wildcard routes: " wilds) {:wilds wilds :routes compiled-routes}))) - (let [names (find-names compiled-routes opts) + (let [names (impl/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)) @@ -230,7 +165,7 @@ compiled-routes) data (impl/fast-map pl) lookup (impl/fast-map nl) - routes (uncompile-routes compiled-routes)] + routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router (router-name [_] @@ -258,20 +193,20 @@ ([compiled-routes] (trie-router compiled-routes {})) ([compiled-routes opts] - (let [names (find-names compiled-routes opts) + (let [names (impl/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]) + (let [{:keys [path-params] :as route} (impl/parse p) f #(if-let [path (impl/path-for route %)] (->Match p data result (impl/url-decode-coll %) path) - (->PartialMatch p data result % path-params))] + (->PartialMatch p data result (impl/url-decode-coll %) path-params))] [(trie/insert pl p (->Match p data result nil nil)) (if name (assoc nl name f) nl)])) [nil {}] compiled-routes) pl (trie/compile pl) lookup (impl/fast-map nl) - routes (uncompile-routes compiled-routes)] + routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router @@ -308,11 +243,11 @@ (ex-info (str ":single-static-path-router requires exactly 1 static route: " compiled-routes) {:routes compiled-routes}))) - (let [[n :as names] (find-names compiled-routes opts) + (let [[n :as names] (impl/find-names compiled-routes opts) [[p data result]] compiled-routes p #?(:clj (.intern ^String p) :cljs p) match (->Match p data result {} p) - routes (uncompile-routes compiled-routes)] + routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router (router-name [_] @@ -347,8 +282,8 @@ ->static-router (if (= 1 (count lookup)) single-static-path-router lookup-router) wildcard-router (trie-router wild opts) static-router (->static-router lookup opts) - names (find-names compiled-routes opts) - routes (uncompile-routes compiled-routes)] + names (impl/find-names compiled-routes opts) + routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router (router-name [_] @@ -378,13 +313,13 @@ ([compiled-routes] (quarantine-router compiled-routes {})) ([compiled-routes opts] - (let [conflicting-paths (-> compiled-routes path-conflicting-routes conflicting-paths) + (let [conflicting-paths (-> compiled-routes impl/path-conflicting-routes impl/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)] + names (impl/find-names compiled-routes opts) + routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router (router-name [_] @@ -407,8 +342,12 @@ (or (match-by-name mixed-router name path-params) (match-by-name linear-router name path-params))))))) +;; +;; Creating Routers +;; + (defn ^:no-doc default-router-options [] - {:lookup name-lookup + {:lookup (fn [[_ {:keys [name]}] _] (if name #{name})) :expand expand :coerce (fn [route _] route) :compile (fn [[_ {:keys [handler]}] _] handler) @@ -435,10 +374,10 @@ (router raw-routes {})) ([raw-routes opts] (let [{:keys [router] :as opts} (merge (default-router-options) opts) - routes (resolve-routes raw-routes opts) - path-conflicting (path-conflicting-routes routes) - name-conflicting (name-conflicting-routes routes) - compiled-routes (compile-routes routes opts) + routes (impl/resolve-routes raw-routes opts) + path-conflicting (impl/path-conflicting-routes routes) + name-conflicting (impl/name-conflicting-routes routes) + compiled-routes (impl/compile-routes routes opts) wilds? (boolean (some impl/wild-route? compiled-routes)) all-wilds? (every? impl/wild-route? compiled-routes) router (cond diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 540e7148..aa33e705 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -1,12 +1,29 @@ (ns ^:no-doc reitit.impl #?(:cljs (:require-macros [reitit.impl])) (:require [clojure.string :as str] - [clojure.set :as set]) + [clojure.set :as set] + [meta-merge.core :as mm] + [reitit.trie :as trie]) #?(:clj (:import (java.util.regex Pattern) (java.util HashMap Map) - (java.net URLEncoder URLDecoder) - (reitit SegmentTrie)))) + (java.net URLEncoder URLDecoder)))) + +(defn normalize [s] + (-> s (trie/split-path) (trie/join-path))) + +(defrecord Route [path path-parts path-params]) + +(defn parse [path] + (let [path #?(:clj (.intern ^String (normalize path)) :cljs (normalize path)) + path-parts (trie/split-path path) + path-params (->> path-parts (remove string?) (map :value) set)] + (map->Route {:path-params path-params + :path-parts path-parts + :path path}))) + +(defn wild-route? [[path]] + (-> path parse :path-params seq boolean)) (defn maybe-map-values "Applies a function to every value of a map, updates the value if not nil. @@ -20,107 +37,128 @@ coll coll)) -(defn segments - "Splits the path into sequence of segments, using `/` char. Assumes that the - path starts with `/`, stripping the first empty segment. e.g. +(defn- -slice-start [[p1 :as p1s] [p2 :as p2s]] + (let [-split (fn [p] + (if-let [i (and p (str/index-of p "/"))] + [(subs p 0 i) (subs p i)] + [p])) + -slash (fn [cp p] + (cond + (not (string? cp)) [cp] + (and (string? cp) (not= (count cp) (count p))) [(subs p (count cp))] + (and (string? p) (not cp)) (-split p))) + -postcut (fn [[p :as pps]] + (let [i (and p (str/index-of p "/"))] + (if (and i (pos? i)) + (concat [(subs p 0 i) (subs p i)] (rest pps)) + pps))) + -tailcut (fn [cp [p :as ps]] (concat (-slash cp p) (rest ps)))] + (if (or (nil? p1) (nil? p2)) + [(-postcut p1s) (-postcut p2s)] + (let [cp (and (string? p1) (string? p2) (trie/common-prefix p1 p2))] + [(-tailcut cp p1s) (-tailcut cp p2s)])))) - (segments \"/a/b/c\") ; => (\"a\" \"b\" \"c\") - (segments \"/a/) ; => (\"a\" \"\")" - [path] - #?(:clj (SegmentTrie/split ^String path) - :cljs (rest (.split path #"/" 666)))) +(defn- -slice-end [x xs] + (let [i (if (string? x) (str/index-of x "/"))] + (if (and (number? i) (pos? i)) + (concat [(subs x i)] xs) + xs))) -;; -;; https://github.com/pedestal/pedestal/blob/master/route/src/io/pedestal/http/route/prefix_tree.clj -;; +(defn conflicting-routes? [route1 route2] + (loop [parts1 (-> route1 first parse :path-parts) + parts2 (-> route2 first parse :path-parts)] + (let [[[s1 & ss1] [s2 & ss2]] (-slice-start parts1 parts2)] + (cond + (= s1 s2 nil) true + (or (nil? s1) (nil? s2)) false + (or (trie/catch-all? s1) (trie/catch-all? s2)) true + (or (trie/wild? s1) (trie/wild? s2)) (recur (-slice-end s1 ss1) (-slice-end s2 ss2)) + (not= s1 s2) false + :else (recur ss1 ss2))))) -(defn wild? [s] - (contains? #{\: \*} (first (str s)))) +(defn walk [raw-routes {:keys [path data routes expand] + :or {data [], routes []} + :as opts}] + (letfn + [(walk-many [p m r] + (reduce #(into %1 (walk-one p m %2)) [] r)) + (walk-one [pacc macc routes] + (if (vector? (first routes)) + (walk-many pacc macc routes) + (when (string? (first routes)) + (let [[path & [maybe-arg :as args]] routes + [data childs] (if (or (vector? maybe-arg) + (and (sequential? maybe-arg) + (sequential? (first maybe-arg))) + (nil? maybe-arg)) + [{} args] + [maybe-arg (rest args)]) + macc (into macc (expand data opts)) + child-routes (walk-many (str pacc path) macc (keep identity childs))] + (if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))] + (walk-one path (mapv identity data) raw-routes))) -(defn catch-all? [s] - (= \* (first (str s)))) +(defn map-data [f routes] + (mapv #(update % 1 f) routes)) -(defn wild-param [s] - (let [ss (str s)] - (if (= \: (first ss)) - (keyword (subs ss 1))))) +(defn merge-data [x] + (reduce + (fn [acc [k v]] + (mm/meta-merge acc {k v})) + {} x)) -(defn catch-all-param [s] - (let [ss (str s)] - (if (= \* (first ss)) - (keyword (subs ss 1))))) +(defn resolve-routes [raw-routes {:keys [coerce] :as opts}] + (cond->> (->> (walk raw-routes opts) (map-data merge-data)) + coerce (into [] (keep #(coerce % opts))))) -(defn wild-or-catch-all-param? [x] - (boolean (or (wild-param x) (catch-all-param x)))) +(defn path-conflicting-routes [routes] + (-> (into {} + (comp (map-indexed (fn [index route] + [route (into #{} + (filter #(conflicting-routes? route %)) + (subvec routes (inc index)))])) + (filter (comp seq second))) + routes) + (not-empty))) -(defn contains-wilds? [path] - (boolean (some wild-or-catch-all-param? (segments path)))) +(defn conflicting-paths [conflicts] + (->> (for [[p pc] conflicts] + (conj (map first pc) (first p))) + (apply concat) + (set))) -;; -;; https://github.com/pedestal/pedestal/blob/master/route/src/io/pedestal/http/route/path.clj -;; +(defn name-conflicting-routes [routes] + (some->> routes + (group-by (comp :name second)) + (remove (comp nil? first)) + (filter (comp pos? count butlast second)) + (seq) + (map (fn [[k v]] [k (set v)])) + (into {}))) -(defn- parse-path-token [out string] - (condp re-matches string - #"^:(.+)$" :>> (fn [[_ token]] - (let [key (keyword token)] - (-> out - (update-in [:path-parts] conj key) - (update-in [:path-params] conj key)))) - #"^\*(.*)$" :>> (fn [[_ token]] - (let [key (keyword token)] - (-> out - (update-in [:path-parts] conj key) - (update-in [:path-params] conj key)))) - (update-in out [:path-parts] conj string))) +(defn find-names [routes _] + (into [] (keep #(-> % second :name)) routes)) -(defn- parse-path - ([pattern] (parse-path {:path-parts [] :path-params #{}} pattern)) - ([accumulated-info pattern] - (if-let [m (re-matches #"/(.*)" pattern)] - (let [[_ path] m] - (reduce parse-path-token - accumulated-info - (str/split path #"/"))) - (throw (ex-info "Routes must start from the root, so they must begin with a '/'" {:pattern pattern}))))) +(defn compile-route [[p m :as route] {:keys [compile] :as opts}] + [p m (if compile (compile route opts))]) -;; -;; Routing (c) Metosin -;; +(defn compile-routes [routes opts] + (into [] (keep #(compile-route % opts) routes))) -(defrecord Route [path path-parts path-params data result]) - -(defn create [[path data result]] - (let [path #?(:clj (.intern ^String path) :cljs path) - {:keys [path-parts path-params]} (parse-path path)] - (map->Route - {:path-params path-params - :path-parts path-parts - :path path - :result result - :data data}))) - -(defn wild-route? [[path]] - (contains-wilds? path)) - -(defn conflicting-routes? [[p1] [p2]] - (loop [[s1 & ss1] (segments p1) - [s2 & ss2] (segments p2)] - (cond - (= s1 s2 nil) true - (or (nil? s1) (nil? s2)) false - (or (catch-all? s1) (catch-all? s2)) true - (or (wild? s1) (wild? s2)) (recur ss1 ss2) - (not= s1 s2) false - :else (recur ss1 ss2)))) +(defn uncompile-routes [routes] + (mapv (comp vec (partial take 2)) routes)) (defn path-for [^Route route path-params] - (if-let [required (:path-params route)] - (if (every? #(contains? path-params %) required) - (->> (:path-parts route) - (map #(get (or path-params {}) % %)) - (str/join \/) - (str "/"))) + (if (:path-params route) + (if-let [parts (reduce + (fn [acc part] + (if (string? part) + (conj acc part) + (if-let [p (get path-params (:value part))] + (conj acc p) + (reduced nil)))) + [] (:path-parts route))] + (apply str parts)) (:path route))) (defn throw-on-missing-path-params [template required path-params] diff --git a/modules/reitit-core/src/reitit/trie.cljc b/modules/reitit-core/src/reitit/trie.cljc index 40384fc2..46b0a3a6 100644 --- a/modules/reitit-core/src/reitit/trie.cljc +++ b/modules/reitit-core/src/reitit/trie.cljc @@ -3,11 +3,16 @@ (:require [clojure.string :as str]) (:import [reitit Trie Trie$Match Trie$Matcher])) +(defrecord Wild [value]) +(defrecord CatchAll [value]) (defrecord Match [data path-params]) (defrecord Node [children wilds catch-all data]) +(defn wild? [x] (instance? Wild x)) +(defn catch-all? [x] (instance? CatchAll x)) + ;; https://stackoverflow.com/questions/8033655/find-longest-common-prefix -(defn- -common-prefix [s1 s2] +(defn common-prefix [s1 s2] (let [max (min (count s1) (count s2))] (loop [i 0] (cond @@ -26,10 +31,10 @@ (keyword (subs s 0 i) (subs s (inc i))) (keyword s))) -(defn- -split [s] +(defn split-path [s] (let [-static (fn [from to] (if-not (= from to) [(subs s from to)])) - -wild (fn [from to] [(-keyword (subs s (inc from) to))]) - -catch-all (fn [from to] [#{(keyword (subs s (inc from) to))}])] + -wild (fn [from to] [(->Wild (-keyword (subs s (inc from) to)))]) + -catch-all (fn [from to] [(->CatchAll (keyword (subs s (inc from) to)))])] (loop [ss nil, from 0, to 0] (if (= to (count s)) (concat ss (-static from to)) @@ -44,6 +49,15 @@ (recur (concat ss (-static from to) (-catch-all to to')) to' to')) (recur ss from (inc to))))))) +(defn join-path [xs] + (reduce + (fn [s x] + (str s (cond + (string? x) x + (instance? Wild x) (str "{" (-> x :value str (subs 1)) "}") + (instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}")))) + "" xs)) + (defn- -node [m] (map->Node (merge {:children {}, :wilds {}, :catch-all {}} m))) @@ -53,11 +67,11 @@ (nil? path) (assoc node :data data) - (keyword? path) - (update-in node [:wilds path] (fn [n] (-insert (or n (-node {})) ps data))) + (instance? Wild path) + (update-in node [:wilds (:value path)] (fn [n] (-insert (or n (-node {})) ps data))) - (set? path) - (assoc-in node [:catch-all path] (-node {:data data})) + (instance? CatchAll path) + (assoc-in node [:catch-all (:value path)] (-node {:data data})) (str/blank? path) (-insert node ps data) @@ -66,7 +80,7 @@ (or (reduce (fn [_ [p n]] - (if-let [cp (-common-prefix p path)] + (if-let [cp (common-prefix p path)] (if (= cp p) ;; insert into child node (let [n' (-insert n (conj ps (subs path (count p))) data)] @@ -89,6 +103,10 @@ (update :children dissoc "")) node'))) +;; +;; public api +;; + (defn insert ([routes] (insert nil routes)) @@ -98,14 +116,14 @@ (insert acc p d)) node routes)) ([node path data] - (-insert (or node (-node {})) (-split path) data))) + (-insert (or node (-node {})) (split-path path) data))) (defn ^Trie$Matcher compile [{:keys [data children wilds catch-all]}] (let [matchers (cond-> [] data (conj (Trie/dataMatcher data)) children (into (for [[p c] children] (Trie/staticMatcher p (compile c)))) wilds (into (for [[p c] wilds] (Trie/wildMatcher p (compile c)))) - catch-all (into (for [[p c] catch-all] (Trie/catchAllMatcher (first p) (:data c)))))] + catch-all (into (for [[p c] catch-all] (Trie/catchAllMatcher p (:data c)))))] (if (rest matchers) (Trie/linearMatcher matchers) (first matchers)))) @@ -182,10 +200,10 @@ (compile) (pretty)) -(-> nil - (insert "/kikka" 2) - (insert "/kikka/kakka/kukka" 3) - (insert "/kikka/:kakka/kurkku" 4) - (insert "/kikka/kuri/{user/doc}/html" 5) +(-> [["/kikka" 2] + ["/kikka/kakka/kukka" 3] + ["/kikka/:kakka/kurkku" 4] + ["/kikka/kuri/{user/doc}/html" 5]] + (insert) (compile) (pretty)) diff --git a/modules/reitit-swagger/src/reitit/swagger.cljc b/modules/reitit-swagger/src/reitit/swagger.cljc index b1551fdb..acd70fd7 100644 --- a/modules/reitit-swagger/src/reitit/swagger.cljc +++ b/modules/reitit-swagger/src/reitit/swagger.cljc @@ -64,12 +64,8 @@ {:name ::swagger :spec ::spec}) -(defn- path->template [path] - (->> (impl/segments path) - (map #(if (impl/wild-or-catch-all-param? %) - (str "{" (subs % 1) "}") %)) - (str/join "/") - (str "/"))) +(defn- swagger-path [path] + (-> path impl/normalize (str/replace #"\{\*" "{"))) (defn create-swagger-handler [] "Create a ring handler to emit swagger spec. Collects all routes from router which have @@ -100,7 +96,7 @@ (strip-top-level-keys swagger))])) transform-path (fn [[p _ c]] (if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))] - [(path->template p) endpoint]))] + [(swagger-path p) endpoint]))] (let [paths (->> router (r/compiled-routes) (filter accept-route) (map transform-path) (into {}))] {:status 200 :body (meta-merge swagger {:paths paths})}))) diff --git a/perf-test/clj/reitit/impl_perf_test.clj b/perf-test/clj/reitit/impl_perf_test.clj index faae130a..1f1f90ed 100644 --- a/perf-test/clj/reitit/impl_perf_test.clj +++ b/perf-test/clj/reitit/impl_perf_test.clj @@ -185,15 +185,6 @@ :c "1+1" :d "1"})) -(defn split! [] - - (suite "split") - - ;; 114ns (String/split) - ;; 82ns (SegmentTrie/split) - (test "Splitting a String") - (test! impl/segments "/olipa/kerran/:avaruus")) - (comment (url-decode!) (url-encode!) diff --git a/perf-test/clj/reitit/opensensors_perf_test.clj b/perf-test/clj/reitit/opensensors_perf_test.clj index 1f7465f7..60b69203 100644 --- a/perf-test/clj/reitit/opensensors_perf_test.clj +++ b/perf-test/clj/reitit/opensensors_perf_test.clj @@ -15,7 +15,8 @@ [io.pedestal.http.route.map-tree :as map-tree] [io.pedestal.http.route.router :as pedestal] [reitit.core :as r] - [criterium.core :as cc])) + [criterium.core :as cc] + [reitit.trie :as trie])) ;; ;; start repl with `lein perf repl` @@ -581,11 +582,11 @@ ;; 735ns (maybe-map-values) ;; 474ns (java-segment-router) ;; 373ms (trie) - (b! "reitit-ring" reitit-ring-f) + #_(b! "reitit-ring" reitit-ring-f) ;; 385ns (java-segment-router, no injects) ;; 271ms (trie) - (b! "reitit-ring-fast" reitit-ring-fast-f) + #_(b! "reitit-ring-fast" reitit-ring-fast-f) ;; 2553ns (linear-router) ;; 630ns (segment-router-backed) @@ -615,6 +616,11 @@ (comment (bench-rest!)) +(-> opensensors-routes + trie/insert + trie/compile + trie/pretty) + (set! *warn-on-reflection* true) (require '[clj-async-profiler.core :as prof]) diff --git a/project.clj b/project.clj index 0b3fa534..e37c2f8d 100644 --- a/project.clj +++ b/project.clj @@ -68,7 +68,10 @@ [org.clojure/clojurescript "1.10.439"] ;; modules dependencies - [metosin/reitit "0.2.13"] + ;[metosin/reitit "0.2.13"] + [meta-merge] + [metosin/schema-tools] + [metosin/spec-tools] [expound "0.7.2"] [orchestra "2018.12.06-2"] diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index d81e6c19..a845f6d3 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -1,6 +1,7 @@ (ns reitit.core-test (:require [clojure.test :refer [deftest testing is are]] - [reitit.core :as r #?@(:cljs [:refer [Match Router]])]) + [reitit.core :as r #?@(:cljs [:refer [Match Router]])] + [reitit.impl :as impl]) #?(:clj (:import (reitit.core Match Router) (clojure.lang ExceptionInfo)))) @@ -136,8 +137,9 @@ ExceptionInfo #"can't create :lookup-router with wildcard routes" (r/lookup-router - (r/resolve-routes - ["/api/:version/ping"] {})))))) + (impl/resolve-routes + ["/api/:version/ping"] + (r/default-router-options))))))) r/lookup-router :lookup-router r/single-static-path-router :single-static-path-router @@ -208,7 +210,7 @@ expected [["/auth/login" {:name :auth/login}] ["/auth/recovery/token/:token" {:name :auth/recovery}] ["/workspace/:project-uuid/:page-uuid" {:name :workspace/page}]]] - (is (= expected (r/resolve-routes routes {}))))) + (is (= expected (impl/resolve-routes routes (r/default-router-options)))))) (testing "ring sample" (let [pong (constantly "ok") @@ -226,7 +228,7 @@ ["/api/admin/user" {:mw [:api :admin], :roles #{:user}}] ["/api/admin/db" {:mw [:api :admin :db], :roles #{:admin}}]] router (r/router routes)] - (is (= expected (r/resolve-routes routes {}))) + (is (= expected (impl/resolve-routes routes (r/default-router-options)))) (is (= (r/map->Match {:template "/api/user/:id/:sub-id" :data {:mw [:api], :parameters {:id "String", :sub-id "String"}} @@ -237,10 +239,10 @@ (deftest conflicting-routes-test (testing "path conflicts" (are [conflicting? data] - (let [routes (r/resolve-routes data {}) + (let [routes (impl/resolve-routes data (r/default-router-options)) conflicts (-> routes - (r/resolve-routes {}) - (r/path-conflicting-routes))] + (impl/resolve-routes (r/default-router-options)) + (impl/path-conflicting-routes))] (if conflicting? (seq conflicts) (nil? conflicts))) true [["/a"] @@ -275,8 +277,8 @@ ["/:b" {}] #{["/c" {}] ["/*d" {}]}, ["/c" {}] #{["/*d" {}]}} (-> [["/a"] ["/:b"] ["/c"] ["/*d"]] - (r/resolve-routes {}) - (r/path-conflicting-routes))))) + (impl/resolve-routes (r/default-router-options)) + (impl/path-conflicting-routes))))) (testing "router with conflicting routes" (testing "throws by default" @@ -331,3 +333,13 @@ (let [router (r/router ["/endpoint" (->Named :kikka)])] (is (= [["/endpoint" {:name :kikka}]] (r/routes router))))) + +(r/router + [["/:abba" ::abba] + ["/abba/1" ::abba2] + ["/:jabba/2" ::jabba2] + ["/:abba/:dabba/doo" ::doo] + ["/abba/dabba/boo/baa" ::baa] + ["/abba/:dabba/boo" ::boo] + ["/:jabba/:dabba/:doo/:daa/*foo" ::wild]] + {:router r/trie-router}) diff --git a/test/cljc/reitit/impl_test.cljc b/test/cljc/reitit/impl_test.cljc index 2ac3cc4b..5ad83786 100644 --- a/test/cljc/reitit/impl_test.cljc +++ b/test/cljc/reitit/impl_test.cljc @@ -2,11 +2,37 @@ (:require [clojure.test :refer [deftest testing is are]] [reitit.impl :as impl])) -(deftest segments-test - (is (= ["api" "ipa" "beer" "craft" "bisse"] - (into [] (impl/segments "/api/ipa/beer/craft/bisse")))) - (is (= ["a" "" "b" "" "c" ""] - (into [] (impl/segments "/a//b//c/"))))) +(deftest normalize-test + (are [path expected] + (is (= expected (impl/normalize path))) + + "/olipa/:kerran/avaruus", "/olipa/{kerran}/avaruus" + "/olipa/{kerran}/avaruus", "/olipa/{kerran}/avaruus" + "/olipa/{a.b/c}/avaruus", "/olipa/{a.b/c}/avaruus" + "/olipa/kerran/*avaruus", "/olipa/kerran/{*avaruus}" + "/olipa/kerran/{*avaruus}", "/olipa/kerran/{*avaruus}" + "/olipa/kerran/{*valvavan.suuri/avaruus}", "/olipa/kerran/{*valvavan.suuri/avaruus}")) + +(deftest conflicting-route-test + (are [c? p1 p2] + (is (= c? (impl/conflicting-routes? [p1] [p2]))) + + true "/a" "/a" + true "/a" "/:a" + true "/a/:b" "/:a/b" + true "/ab/:b" "/:a/ba" + true "/*a" "/:a/ba/ca" + + true "/a" "/{a}" + true "/a/{b}" "/{a}/b" + true "/ab/{b}" "/{a}/ba" + true "/{*a}" "/{a}/ba/ca" + + false "/a" "/:a/b" + false "/a" "/:a/b" + + false "/a" "/{a}/b" + false "/a" "/{a}/b")) (deftest strip-nils-test (is (= {:a 1, :c false} (impl/strip-nils {:a 1, :b nil, :c false})))) diff --git a/test/cljc/reitit/swagger_test.clj b/test/cljc/reitit/swagger_test.clj index 4eefcb72..75305021 100644 --- a/test/cljc/reitit/swagger_test.clj +++ b/test/cljc/reitit/swagger_test.clj @@ -32,16 +32,16 @@ :handler (fn [{{{:keys [x y]} :query {:keys [z]} :path} :parameters}] {:status 200, :body {:total (+ x y z)}})} - :post {:summary "plus with body" + :post {:summary "plus with body" :parameters {:body [int?] :path {:z int?}} - :swagger {:responses {400 {:schema {:type "string"} - :description "kosh"}}} - :responses {200 {:body {:total int?}} - 500 {:description "fail"}} - :handler (fn [{{{:keys [z]} :path - xs :body} :parameters}] - {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] + :swagger {:responses {400 {:schema {:type "string"} + :description "kosh"}}} + :responses {200 {:body {:total int?}} + 500 {:description "fail"}} + :handler (fn [{{{:keys [z]} :path + xs :body} :parameters}] + {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] ["/schema" {:coercion schema/coercion} ["/plus/*z" @@ -72,8 +72,8 @@ (is (= {:body {:total 7}, :status 200} (app {:request-method :post - :uri "/api/spec/plus/3" - :body-params [1 3]})))) + :uri "/api/spec/plus/3" + :body-params [1 3]})))) (testing "schema" (is (= {:body {:total 6}, :status 200} (app @@ -142,28 +142,28 @@ :description "kosh"} 500 {:description "fail"}} :summary "plus"} - :post {:parameters [{:in "body", - :name "", + :post {:parameters [{:in "body", + :name "", :description "", - :required true, - :schema {:type "array", - :items {:type "integer", - :format "int64"}}} - {:in "path" - :name "z" + :required true, + :schema {:type "array", + :items {:type "integer", + :format "int64"}}} + {:in "path" + :name "z" :description "" - :type "integer" - :required true - :format "int64"}] - :responses {200 {:description "" - :schema {:properties {"total" {:format "int64" - :type "integer"}} - :required ["total"] - :type "object"}} - 400 {:schema {:type "string"} - :description "kosh"} - 500 {:description "fail"}} - :summary "plus with body"}}}}] + :type "integer" + :required true + :format "int64"}] + :responses {200 {:description "" + :schema {:properties {"total" {:format "int64" + :type "integer"}} + :required ["total"] + :type "object"}} + 400 {:schema {:type "string"} + :description "kosh"} + 500 {:description "fail"}} + :summary "plus with body"}}}}] (is (= expected spec)) (testing "ring-async swagger-spec"