Free from the regex!

This commit is contained in:
Tommi Reiman 2019-02-02 17:15:59 +02:00
parent 8755e19f78
commit 6f902d118a
11 changed files with 303 additions and 275 deletions

View file

@ -34,7 +34,6 @@ public class Trie {
hasPlus = true;
}
}
System.err.println();
return decode(chars, offset, count, hasPercent, hasPlus);
}

View file

@ -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

View file

@ -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]

View file

@ -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))

View file

@ -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})})))

View file

@ -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!)

View file

@ -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])

View file

@ -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"]

View file

@ -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})

View file

@ -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}))))

View file

@ -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"