Merge pull request #8 from metosin/PathFor

Lot's of small improvements
This commit is contained in:
Tommi Reiman 2017-08-10 15:23:42 +03:00 committed by GitHub
commit 5646494388
4 changed files with 128 additions and 39 deletions

View file

@ -96,8 +96,9 @@
(call))) (call)))
;; 1.0µs (-94%) ;; 1.0µs (-94%)
;; 770ns (-95%, -23%)
(title "reitit") (title "reitit")
(let [call #(reitit/match-route reitit-routes "/workspace/1/1")] (let [call #(reitit/match reitit-routes "/workspace/1/1")]
(assert (call)) (assert (call))
(cc/quick-bench (cc/quick-bench
(call)))) (call))))

View file

@ -1,6 +1,8 @@
(ns reitit.core (ns reitit.core
(:require [meta-merge.core :refer [meta-merge]] (:require [meta-merge.core :refer [meta-merge]]
[reitit.regex :as regex])) [reitit.impl :as impl #?@(:cljs [:refer [Route]])])
#?(:clj
(:import (reitit.impl Route))))
(defprotocol Expand (defprotocol Expand
(expand [this])) (expand [this]))
@ -57,31 +59,65 @@
(->> (walk data opts) (map-meta merge-meta))) (->> (walk data opts) (map-meta merge-meta)))
(defprotocol Routing (defprotocol Routing
(match-route [this path]) (routes [this])
(path-for [this name] [this name parameters])) (match [this path])
(by-name [this name] [this name parameters]))
(defrecord LinearRouter [routes] (defrecord Match [template meta path params])
(defrecord LinearRouter [routes data lookup]
Routing Routing
(match-route [_ path] (routes [_]
routes)
(match [_ path]
(reduce (reduce
(fn [acc [p m matcher]] (fn [acc ^Route route]
(if-let [params (matcher path)] (if-let [params ((:matcher route) path)]
(reduced (assoc m :route-params params)))) (reduced (->Match (:path route) (:meta route) path params))))
nil routes))) nil data))
(by-name [_ name]
((lookup name) nil))
(by-name [_ name params]
((lookup name) params)))
(defrecord LookupRouter [routes] (defn linear-router [routes]
(->LinearRouter
routes
(mapv (partial apply impl/create) routes)
(->> (for [[p {:keys [name] :as meta}] routes
:when name
:let [route (impl/create p meta)]]
[name (fn [params]
(->Match p meta (impl/path-for route params) params))])
(into {}))))
(defrecord LookupRouter [routes data lookup]
Routing Routing
(match-route [_ path] (routes [_]
(routes path))) routes)
(match [_ path]
(data path))
(by-name [_ name]
((lookup name) nil))
(by-name [_ name params]
((lookup name) params)))
(defn lookup-router [routes]
(->LookupRouter
routes
(->> (for [[p meta] routes]
[p (->Match p meta p {})])
(into {}))
(->> (for [[p {:keys [name] :as meta}] routes
:when name]
[name (fn [params]
(->Match p meta p params))])
(into {}))))
(defn router (defn router
([data] ([data]
(router data {})) (router data {}))
([data opts] ([data opts]
(let [routes (resolve-routes data opts)] (let [routes (resolve-routes data opts)]
(if (some regex/contains-wilds? (map first routes)) ((if (some impl/contains-wilds? (map first routes))
(->LinearRouter linear-router lookup-router) routes))))
(for [[p m] routes]
[p m (regex/matcher p)]))
(->LookupRouter
(into {} routes))))))

View file

@ -10,8 +10,9 @@
; ;
; You must not remove this notice, or any other, from this software. ; You must not remove this notice, or any other, from this software.
(ns reitit.regex (ns reitit.impl
(:require [clojure.string :as str]) (:require [clojure.string :as str]
[clojure.set :as set])
(:import #?(:clj (java.util.regex Pattern)))) (:import #?(:clj (java.util.regex Pattern))))
;; ;;
@ -97,12 +98,34 @@
(wild? (first parts))))) (wild? (first parts)))))
;; ;;
;; Routing ;; Routing (c) Metosin
;; ;;
(defn matcher [path] (defrecord Route [path matcher parts params meta])
(defn create [path meta]
(if (contains-wilds? path) (if (contains-wilds? path)
(as-> (parse-path path) $ (as-> (parse-path path) $
(assoc $ :path-re (path-regex $)) (assoc $ :path-re (path-regex $))
(path-matcher $)) (merge $ {:path path
#(if (= path %) {}))) :matcher (path-matcher $)
:meta meta})
(dissoc $ :path-re :path-constraints)
(update $ :path-params set)
(set/rename-keys $ {:path-parts :parts
:path-params :params})
(map->Route $))
(map->Route {:path path
:meta meta
:matcher #(if (= path %) {})})))
(defn path-for [^Route route params]
(when-not (every? #(contains? params %) (:params route))
(let [defined (-> params keys set)
required (:params route)
missing (clojure.set/difference required defined)]
(throw
(ex-info
(str "missing path-params for route '" (:path route) "': " missing)
{:params params, :required required}))))
(str "/" (str/join \/ (map #(get params % %) (:parts route)))))

View file

@ -1,25 +1,51 @@
(ns reitit.core-test (ns reitit.core-test
(:require [clojure.test :refer [deftest testing is are]] (:require [clojure.test :refer [deftest testing is are]]
#?(:clj [reitit.core :as reitit] [reitit.core :as reitit #?@(:cljs [:refer [Match LinearRouter LookupRouter]])])
:cljs [reitit.core :as reitit :refer [LinearRouter LookupRouter]]))
#?(:clj #?(:clj
(:import (reitit.core LinearRouter LookupRouter)))) (:import (reitit.core Match LinearRouter LookupRouter)
(clojure.lang ExceptionInfo))))
(deftest reitit-test (deftest reitit-test
(testing "linear router" (testing "linear router"
(let [router (reitit/router ["/api" (let [router (reitit/router ["/api" ["/ipa" ["/:size" ::beer]]])]
["/ipa"
["/:size"]]])]
(is (instance? LinearRouter router)) (is (instance? LinearRouter router))
(is (map? (reitit/match-route router "/api/ipa/large"))))) (is (= [["/api/ipa/:size" {:name ::beer}]]
(reitit/routes router)))
(is (= (reitit/map->Match
{:template "/api/ipa/:size"
:meta {:name ::beer}
:path "/api/ipa/large"
:params {:size "large"}})
(reitit/match router "/api/ipa/large")))
(is (= (reitit/map->Match
{:template "/api/ipa/:size"
:meta {:name ::beer}
:path "/api/ipa/large"
:params {:size "large"}})
(reitit/by-name router ::beer {:size "large"})))
(is (thrown-with-msg?
ExceptionInfo
#"^missing path-params for route '/api/ipa/:size': \#\{:size\}$"
(reitit/by-name router ::beer)))))
(testing "lookup router" (testing "lookup router"
(let [router (reitit/router ["/api" (let [router (reitit/router ["/api" ["/ipa" ["/large" ::beer]]])]
["/ipa"
["/large"]]])]
(is (instance? LookupRouter router)) (is (instance? LookupRouter router))
(is (map? (reitit/match-route router "/api/ipa/large"))))) (is (= [["/api/ipa/large" {:name ::beer}]]
(reitit/routes router)))
(is (= (reitit/map->Match
{:template "/api/ipa/large"
:meta {:name ::beer}
:path "/api/ipa/large"
:params {}})
(reitit/match router "/api/ipa/large")))
(is (= (reitit/map->Match
{:template "/api/ipa/large"
:meta {:name ::beer}
:path "/api/ipa/large"
:params {:size "large"}})
(reitit/by-name router ::beer {:size "large"})))))
(testing "bide sample" (testing "bide sample"
(let [routes [["/auth/login" :auth/login] (let [routes [["/auth/login" :auth/login]
@ -47,7 +73,10 @@
["/api/admin/db" {:mw [:api :admin :db], :roles #{:admin}}]] ["/api/admin/db" {:mw [:api :admin :db], :roles #{:admin}}]]
router (reitit/router routes)] router (reitit/router routes)]
(is (= expected (reitit/resolve-routes routes {}))) (is (= expected (reitit/resolve-routes routes {})))
(is (= {:mw [:api], :parameters {:id String, :sub-id String} (is (= (reitit/map->Match
:route-params {:id "1", :sub-id "2"}} {:template "/api/user/:id/:sub-id"
(reitit/match-route router "/api/user/1/2")))))) :meta {:mw [:api], :parameters {:id String, :sub-id String}}
:path "/api/user/1/2"
:params {:id "1", :sub-id "2"}})
(reitit/match router "/api/user/1/2"))))))