From c9281f0e893355a18d1b76fa386706f230b58720 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Sun, 9 Jun 2019 20:29:03 +0300 Subject: [PATCH 1/2] support `:parameter-syntax` option in router --- modules/reitit-core/src/reitit/core.cljc | 18 ++--- modules/reitit-core/src/reitit/impl.cljc | 30 ++++--- modules/reitit-core/src/reitit/trie.cljc | 61 +++++++++----- .../reitit-swagger/src/reitit/swagger.cljc | 12 ++- test/cljc/reitit/core_test.cljc | 4 +- test/cljc/reitit/impl_test.cljc | 30 +------ test/cljc/reitit/trie_test.cljc | 80 ++++++++++++++++++- 7 files changed, 151 insertions(+), 84 deletions(-) diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index 573ba5b2..3aafa718 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -88,7 +88,7 @@ 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/parse p) + (let [{:keys [path-params] :as route} (impl/parse p opts) f #(if-let [path (impl/path-for route %)] (->Match p data result (impl/url-decode-coll %) path) (->PartialMatch p data result (impl/url-decode-coll %) path-params))] @@ -131,7 +131,7 @@ ([compiled-routes] (lookup-router compiled-routes {})) ([compiled-routes opts] - (when-let [wilds (seq (filter impl/wild-route? compiled-routes))] + (when-let [wilds (seq (filter (impl/->wild-route? opts) compiled-routes))] (exception/fail! (str "can't create :lookup-router with wildcard routes: " wilds) {:wilds wilds @@ -184,7 +184,7 @@ 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/parse p) + (let [{:keys [path-params] :as route} (impl/parse p opts) f #(if-let [path (impl/path-for route %)] (->Match p data result (impl/url-decode-coll %) path) (->PartialMatch p data result (impl/url-decode-coll %) path-params))] @@ -227,7 +227,7 @@ ([compiled-routes] (single-static-path-router compiled-routes {})) ([compiled-routes opts] - (when (or (not= (count compiled-routes) 1) (some impl/wild-route? compiled-routes)) + (when (or (not= (count compiled-routes) 1) (some (impl/->wild-route? opts) compiled-routes)) (exception/fail! (str ":single-static-path-router requires exactly 1 static route: " compiled-routes) {:routes compiled-routes})) @@ -266,7 +266,7 @@ ([compiled-routes] (mixed-router compiled-routes {})) ([compiled-routes opts] - (let [{wild true, lookup false} (group-by impl/wild-route? compiled-routes) + (let [{wild true, lookup false} (group-by (impl/->wild-route? opts) compiled-routes) ->static-router (if (= 1 (count lookup)) single-static-path-router lookup-router) wildcard-router (trie-router wild opts) static-router (->static-router lookup opts) @@ -301,7 +301,7 @@ ([compiled-routes] (quarantine-router compiled-routes {})) ([compiled-routes opts] - (let [conflicting-paths (-> compiled-routes impl/path-conflicting-routes impl/conflicting-paths) + (let [conflicting-paths (-> compiled-routes (impl/path-conflicting-routes opts) impl/conflicting-paths) conflicting? #(contains? conflicting-paths (first %)) {conflicting true, non-conflicting false} (group-by conflicting? compiled-routes) linear-router (linear-router conflicting opts) @@ -366,11 +366,11 @@ (let [{:keys [router] :as opts} (merge (default-router-options) opts)] (try (let [routes (impl/resolve-routes raw-routes opts) - path-conflicting (impl/path-conflicting-routes routes) + path-conflicting (impl/path-conflicting-routes routes opts) 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) + wilds? (boolean (some (impl/->wild-route? opts) compiled-routes)) + all-wilds? (every? (impl/->wild-route? opts) compiled-routes) router (cond router router (and (= 1 (count compiled-routes)) (not wilds?)) single-static-path-router diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index b2aeb250..0fddc92b 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -11,18 +11,19 @@ (java.util HashMap Map) (java.net URLEncoder URLDecoder)))) -(defrecord Route [path path-parts path-params]) - -(defn parse [path] - (let [path #?(:clj (.intern ^String (trie/normalize path)) :cljs (trie/normalize path)) - path-parts (trie/split-path path) +(defn parse [path opts] + (let [path #?(:clj (.intern ^String (trie/normalize path opts)) :cljs (trie/normalize path opts)) + path-parts (trie/split-path path opts) path-params (->> path-parts (remove string?) (map :value) set)] - (map->Route {:path-params path-params - :path-parts path-parts - :path path}))) + {:path-params path-params + :path-parts path-parts + :path path})) -(defn wild-route? [[path]] - (-> path parse :path-params seq boolean)) +(defn wild-path? [path opts] + (-> path (parse opts) :path-params seq boolean)) + +(defn ->wild-route? [opts] + (fn [[path]] (-> path (parse opts) :path-params seq boolean))) (defn maybe-map-values "Applies a function to every value of a map, updates the value if not nil. @@ -74,14 +75,11 @@ (cond->> (->> (walk raw-routes opts) (map-data merge-data)) coerce (into [] (keep #(coerce % opts))))) -(defn conflicting-routes? [route1 route2] - (trie/conflicting-paths? (first route1) (first route2))) - -(defn path-conflicting-routes [routes] +(defn path-conflicting-routes [routes opts] (-> (into {} (comp (map-indexed (fn [index route] [route (into #{} - (filter (partial conflicting-routes? route)) + (filter #(trie/conflicting-paths? (first route) (first %) opts)) (subvec routes (inc index)))])) (filter (comp seq second))) routes) @@ -114,7 +112,7 @@ (defn uncompile-routes [routes] (mapv (comp vec (partial take 2)) routes)) -(defn path-for [^Route route path-params] +(defn path-for [route path-params] (if (:path-params route) (if-let [parts (reduce (fn [acc part] diff --git a/modules/reitit-core/src/reitit/trie.cljc b/modules/reitit-core/src/reitit/trie.cljc index 526fc7a4..99d13960 100644 --- a/modules/reitit-core/src/reitit/trie.cljc +++ b/modules/reitit-core/src/reitit/trie.cljc @@ -5,6 +5,12 @@ #?(:clj (:import [reitit Trie Trie$Match Trie$Matcher] (java.net URLDecoder)))) +(defn ^:no-doc into-set [x] + (cond + (or (set? x) (sequential? x)) (set x) + (nil? x) #{} + :else (conj #{} x))) + (defrecord Wild [value]) (defrecord CatchAll [value]) (defrecord Match [params data]) @@ -51,25 +57,36 @@ (keyword (subs s 0 i) (subs s (inc i))) (keyword s))) -(defn split-path [s] - (let [-static (fn [from to] (if-not (= from to) [(subs s from to)])) +(defn split-path [s {:keys [parameter-syntax] :or {parameter-syntax #{:bracket :colon}}}] + (let [bracket? (-> parameter-syntax (into-set) :bracket) + colon? (-> parameter-syntax (into-set) :colon) + -static (fn [from to] (if-not (= from to) [(subs s 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)) - (case (get s to) - \{ (let [to' (or (str/index-of s "}" to) (ex/fail! ::unclosed-brackets {:path s}))] - (if (= \* (get s (inc to))) - (recur (concat ss (-static from to) (-catch-all (inc to) to')) (long (inc to')) (long (inc to'))) - (recur (concat ss (-static from to) (-wild to to')) (long (inc to')) (long (inc to'))))) - \: (let [to' (or (str/index-of s "/" to) (count s))] - (if (= 1 (- to' to)) - (recur ss from (inc to)) - (recur (concat ss (-static from to) (-wild to to')) (long to') (long to')))) - \* (let [to' (count s)] - (recur (concat ss (-static from to) (-catch-all to to')) (long to') (long to'))) - (recur ss from (inc to))))))) + (let [c (get s to)] + (cond + + (and bracket? (= \{ c)) + (let [to' (or (str/index-of s "}" to) (ex/fail! ::unclosed-brackets {:path s}))] + (if (= \* (get s (inc to))) + (recur (concat ss (-static from to) (-catch-all (inc to) to')) (long (inc to')) (long (inc to'))) + (recur (concat ss (-static from to) (-wild to to')) (long (inc to')) (long (inc to'))))) + + (and colon? (= \: c)) + (let [to' (or (str/index-of s "/" to) (count s))] + (if (= 1 (- to' to)) + (recur ss from (inc to)) + (recur (concat ss (-static from to) (-wild to to')) (long to') (long to')))) + + (and colon? (= \* c)) + (let [to' (count s)] + (recur (concat ss (-static from to) (-catch-all to to')) (long to') (long to'))) + + :else + (recur ss from (inc to)))))))) (defn join-path [xs] (reduce @@ -80,8 +97,8 @@ (instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}")))) "" xs)) -(defn normalize [s] - (-> s (split-path) (join-path))) +(defn normalize [s opts] + (-> s (split-path opts) (join-path))) ;; ;; Conflict Resolution @@ -115,9 +132,9 @@ (concat [(subs x i)] xs) xs))) -(defn conflicting-paths? [path1 path2] - (loop [parts1 (split-path path1) - parts2 (split-path path2)] +(defn conflicting-paths? [path1 path2 opts] + (loop [parts1 (split-path path1 opts) + parts2 (split-path path2 opts)] (let [[[s1 & ss1] [s2 & ss2]] (-slice-start parts1 parts2)] (cond (= s1 s2 nil) true @@ -314,10 +331,10 @@ node routes)) ([node path data] (insert node path data nil)) - ([node path data {::keys [parameters] :or {parameters map-parameters}}] - (let [parts (split-path path) + ([node path data {::keys [parameters] :or {parameters map-parameters} :as opts}] + (let [parts (split-path path opts) params (parameters (->> parts (remove string?) (map :value)))] - (-insert (or node (-node {})) (split-path path) path params data)))) + (-insert (or node (-node {})) (split-path path opts) path params data)))) (defn compiler "Returns a default [[TrieCompiler]]." diff --git a/modules/reitit-swagger/src/reitit/swagger.cljc b/modules/reitit-swagger/src/reitit/swagger.cljc index 0338c43d..567c5ab6 100644 --- a/modules/reitit-swagger/src/reitit/swagger.cljc +++ b/modules/reitit-swagger/src/reitit/swagger.cljc @@ -1,6 +1,5 @@ (ns reitit.swagger (:require [reitit.core :as r] - [reitit.impl :as impl] [meta-merge.core :refer [meta-merge]] [clojure.spec.alpha :as s] [clojure.set :as set] @@ -65,8 +64,8 @@ {:name ::swagger :spec ::spec}) -(defn- swagger-path [path] - (-> path trie/normalize (str/replace #"\{\*" "{"))) +(defn- swagger-path [path opts] + (-> path (trie/normalize opts) (str/replace #"\{\*" "{"))) (defn create-swagger-handler [] "Create a ring handler to emit swagger spec. Collects all routes from router which have @@ -74,15 +73,14 @@ (fn create-swagger ([{:keys [::r/router ::r/match :request-method]}] (let [{:keys [id] :or {id ::default} :as swagger} (-> match :result request-method :data :swagger) - ->set (fn [x] (if (or (set? x) (sequential? x)) (set x) (conj #{} x))) - ids (->set id) + ids (trie/into-set id) strip-top-level-keys #(dissoc % :id :info :host :basePath :definitions :securityDefinitions) strip-endpoint-keys #(dissoc % :id :parameters :responses :summary :description) swagger (->> (strip-endpoint-keys swagger) (merge {:swagger "2.0" :x-id ids})) accept-route (fn [route] - (-> route second :swagger :id (or ::default) ->set (set/intersection ids) seq)) + (-> route second :swagger :id (or ::default) (trie/into-set) (set/intersection ids) seq)) transform-endpoint (fn [[method {{:keys [coercion no-doc swagger] :as data} :data middleware :middleware interceptors :interceptors}]] @@ -97,7 +95,7 @@ (strip-top-level-keys swagger))])) transform-path (fn [[p _ c]] (if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))] - [(swagger-path p) endpoint]))] + [(swagger-path p (r/options router)) endpoint]))] (let [map-in-order #(->> % (apply concat) (apply array-map)) paths (->> router (r/compiled-routes) (filter accept-route) (map transform-path) map-in-order)] {:status 200 diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index 4d9ee770..b29fc18c 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -292,7 +292,7 @@ (let [routes (impl/resolve-routes data (r/default-router-options)) conflicts (-> routes (impl/resolve-routes (r/default-router-options)) - (impl/path-conflicting-routes))] + (impl/path-conflicting-routes nil))] (if conflicting? (seq conflicts) (nil? conflicts))) true [["/a"] @@ -328,7 +328,7 @@ ["/c" {}] #{["/*d" {}]}} (-> [["/a"] ["/:b"] ["/c"] ["/*d"]] (impl/resolve-routes (r/default-router-options)) - (impl/path-conflicting-routes))))) + (impl/path-conflicting-routes nil))))) (testing "router with conflicting routes" (testing "throws by default" diff --git a/test/cljc/reitit/impl_test.cljc b/test/cljc/reitit/impl_test.cljc index bb9b24e8..d8ff062f 100644 --- a/test/cljc/reitit/impl_test.cljc +++ b/test/cljc/reitit/impl_test.cljc @@ -2,27 +2,6 @@ (:require [clojure.test :refer [deftest testing is are]] [reitit.impl :as impl])) -(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})))) @@ -188,8 +167,7 @@ "%2B632+905+123+4567" "+632 905 123 4567")) (deftest parse-test - (is (= (impl/map->Route - {:path "https://google.com" - :path-parts ["https://google.com"] - :path-params #{}}) - (impl/parse "https://google.com")))) + (is (= {:path "https://google.com" + :path-parts ["https://google.com"] + :path-params #{}} + (impl/parse "https://google.com" nil)))) diff --git a/test/cljc/reitit/trie_test.cljc b/test/cljc/reitit/trie_test.cljc index 1592123c..b68e455b 100644 --- a/test/cljc/reitit/trie_test.cljc +++ b/test/cljc/reitit/trie_test.cljc @@ -2,16 +2,92 @@ (:require [clojure.test :refer [deftest testing is are]] [reitit.trie :as trie])) +(deftest into-set-test + (is (= #{} (trie/into-set nil))) + (is (= #{} (trie/into-set []))) + (is (= #{1} (trie/into-set 1))) + (is (= #{1 2} (trie/into-set [1 2 1])))) + +(deftest conflicting-paths?-test + (are [c? p1 p2] + (is (= c? (trie/conflicting-paths? p1 p2 nil))) + + 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 split-path-test + (testing "colon" + (doseq [parameter-syntax [:colon #{:colon}]] + (are [path expected] + (is (= expected (trie/split-path path {:parameter-syntax parameter-syntax}))) + + "/olipa/:kerran/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] + "/olipa/{kerran}/avaruus", ["/olipa/{kerran}/avaruus"] + "/olipa/{a.b/c}/avaruus", ["/olipa/{a.b/c}/avaruus"] + "/olipa/kerran/*avaruus", ["/olipa/kerran/" (trie/->CatchAll :avaruus)] + "/olipa/kerran/{*avaruus}", ["/olipa/kerran/{" (trie/->CatchAll (keyword "avaruus}"))] + "/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/{" (trie/->CatchAll (keyword "valtavan.suuri/avaruus}"))]))) + + (testing "bracket" + (doseq [parameter-syntax [:bracket #{:bracket}]] + (are [path expected] + (is (= expected (trie/split-path path {:parameter-syntax parameter-syntax}))) + + "/olipa/:kerran/avaruus", ["/olipa/:kerran/avaruus"] + "/olipa/{kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] + "/olipa/{a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"] + "/olipa/kerran/*avaruus", ["/olipa/kerran/*avaruus"] + "/olipa/kerran/{*avaruus}", ["/olipa/kerran/" (trie/->CatchAll :avaruus)] + "/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/" (trie/->CatchAll :valtavan.suuri/avaruus)]))) + + (testing "both" + (doseq [parameter-syntax [#{:bracket :colon}]] + (are [path expected] + (is (= expected (trie/split-path path {:parameter-syntax parameter-syntax}))) + + "/olipa/:kerran/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] + "/olipa/{kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] + "/olipa/{a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"] + "/olipa/kerran/*avaruus", ["/olipa/kerran/" (trie/->CatchAll :avaruus)] + "/olipa/kerran/{*avaruus}", ["/olipa/kerran/" (trie/->CatchAll :avaruus)] + "/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/" (trie/->CatchAll :valtavan.suuri/avaruus)]))) + + (testing "nil" + (doseq [parameter-syntax [nil]] + (are [path expected] + (is (= expected (trie/split-path path {:parameter-syntax parameter-syntax}))) + + "/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/{*valtavan.suuri/avaruus}", ["/olipa/kerran/{*valtavan.suuri/avaruus}"])))) + (deftest normalize-test (are [path expected] - (is (= expected (trie/normalize path))) + (is (= expected (trie/normalize path nil))) "/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}")) + "/olipa/kerran/{*valtavan.suuri/avaruus}", "/olipa/kerran/{*valtavan.suuri/avaruus}")) (deftest tests (is (= (trie/->Match {} {:a 1}) From 46897f3927412c26e40828c53186967c52e527e3 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Sun, 9 Jun 2019 20:46:20 +0300 Subject: [PATCH 2/2] Update docs --- CHANGELOG.md | 18 ++++++++++++++++++ doc/advanced/configuring_routers.md | 5 +++-- doc/basics/route_syntax.md | 22 +++++++++++++++++++++- modules/reitit-core/src/reitit/core.cljc | 5 +++-- modules/reitit-core/src/reitit/trie.cljc | 6 +++--- test/cljc/reitit/trie_test.cljc | 16 ++++++++-------- 6 files changed, 56 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efbe5726..f48d0ca0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,24 @@ We use [Break Versioning][breakver]. The version numbers follow a `. (r/router + ["http://localhost:8080/api/user/{id}" ::user-by-id] + {:syntax :bracket}) + (r/match-by-path "http://localhost:8080/api/user/123")) +;#Match{:template "http://localhost:8080/api/user/{id}", +; :data {:name :user/user-by-id}, +; :result nil, +; :path-params {:id "123"}, +; :path "http://localhost:8080/api/user/123"} +``` + ## 0.3.7 (2019-05-25) ### `reitit-pedestal` diff --git a/doc/advanced/configuring_routers.md b/doc/advanced/configuring_routers.md index b624355d..4cc30be9 100644 --- a/doc/advanced/configuring_routers.md +++ b/doc/advanced/configuring_routers.md @@ -2,12 +2,13 @@ Routers can be configured via options. The following options are available for the `reitit.core/router`: -| key | description | -|--------------|-------------| +| key | description +|--------------|------------- | `:path` | Base-path for routes | `:routes` | Initial resolved routes (default `[]`) | `:data` | Initial route data (default `{}`) | `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this +| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon}) | `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`) | `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` | `:compile` | Function of `route opts => result` to compile a route handler diff --git a/doc/basics/route_syntax.md b/doc/basics/route_syntax.md index bca58569..3794a2d7 100644 --- a/doc/basics/route_syntax.md +++ b/doc/basics/route_syntax.md @@ -4,7 +4,7 @@ Routes are defined as vectors of String path and optional (non-sequential) route Routes can be wrapped in vectors and lists and `nil` routes are ignored. -Paths can have path-parameters (`:id`) or catch-all-parameters (`*path`). Since version `0.3.0`, parameters can also be wrapped in brackets, enabling use of qualified keywords `{user/id}`, `{*user/path}`. The non-bracket syntax might be deprecated later. +Paths can have path-parameters (`:id`) or catch-all-parameters (`*path`). Parameters can also be wrapped in brackets, enabling use of qualified keywords `{user/id}`, `{*user/path}`. By default, both syntaxes are supported, see [configuring routers](../advanced/configuring_routers.md) on how to change this. ### Examples @@ -129,3 +129,23 @@ Routes are just data, so it's easy to create them programmatically: ; ["/add-user" {:post {:interceptors [add-user]}}] ; ["/add-order" {:post {:interceptors [add-order]}}])] ``` + +### Explicit path-parameter syntax + +Router options `:syntax` allows the path-parameter syntax to be explicitely defined. It takes a keyword or set of keywords as a value. Valid values are `:colon` and `:bracket`. Default value is `#{:colon :bracket}`. + +Supporting only `:bracket` syntax: + +```clj +(require '[reitit.core :as r]) + +(-> (r/router + ["http://localhost:8080/api/user/{id}" ::user-by-id] + {:syntax :bracket}) + (r/match-by-path "http://localhost:8080/api/user/123")) +;#Match{:template "http://localhost:8080/api/user/{id}", +; :data {:name :user/user-by-id}, +; :result nil, +; :path-params {:id "123"}, +; :path "http://localhost:8080/api/user/123"} +``` diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index 3aafa718..26f61ab5 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -347,12 +347,13 @@ Selects implementation based on route details. The following options are available: - | key | description | - | -------------|-------------| + | key | description + | -------------|------------- | `:path` | Base-path for routes | `:routes` | Initial resolved routes (default `[]`) | `:data` | Initial route data (default `{}`) | `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this + | `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon}) | `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`) | `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` | `:compile` | Function of `route opts => result` to compile a route handler diff --git a/modules/reitit-core/src/reitit/trie.cljc b/modules/reitit-core/src/reitit/trie.cljc index 99d13960..6eaa417f 100644 --- a/modules/reitit-core/src/reitit/trie.cljc +++ b/modules/reitit-core/src/reitit/trie.cljc @@ -57,9 +57,9 @@ (keyword (subs s 0 i) (subs s (inc i))) (keyword s))) -(defn split-path [s {:keys [parameter-syntax] :or {parameter-syntax #{:bracket :colon}}}] - (let [bracket? (-> parameter-syntax (into-set) :bracket) - colon? (-> parameter-syntax (into-set) :colon) +(defn split-path [s {:keys [syntax] :or {syntax #{:bracket :colon}}}] + (let [bracket? (-> syntax (into-set) :bracket) + colon? (-> syntax (into-set) :colon) -static (fn [from to] (if-not (= from to) [(subs s 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)))])] diff --git a/test/cljc/reitit/trie_test.cljc b/test/cljc/reitit/trie_test.cljc index b68e455b..7eca95fe 100644 --- a/test/cljc/reitit/trie_test.cljc +++ b/test/cljc/reitit/trie_test.cljc @@ -31,9 +31,9 @@ (deftest split-path-test (testing "colon" - (doseq [parameter-syntax [:colon #{:colon}]] + (doseq [syntax [:colon #{:colon}]] (are [path expected] - (is (= expected (trie/split-path path {:parameter-syntax parameter-syntax}))) + (is (= expected (trie/split-path path {:syntax syntax}))) "/olipa/:kerran/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] "/olipa/{kerran}/avaruus", ["/olipa/{kerran}/avaruus"] @@ -43,9 +43,9 @@ "/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/{" (trie/->CatchAll (keyword "valtavan.suuri/avaruus}"))]))) (testing "bracket" - (doseq [parameter-syntax [:bracket #{:bracket}]] + (doseq [syntax [:bracket #{:bracket}]] (are [path expected] - (is (= expected (trie/split-path path {:parameter-syntax parameter-syntax}))) + (is (= expected (trie/split-path path {:syntax syntax}))) "/olipa/:kerran/avaruus", ["/olipa/:kerran/avaruus"] "/olipa/{kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] @@ -55,9 +55,9 @@ "/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/" (trie/->CatchAll :valtavan.suuri/avaruus)]))) (testing "both" - (doseq [parameter-syntax [#{:bracket :colon}]] + (doseq [syntax [#{:bracket :colon}]] (are [path expected] - (is (= expected (trie/split-path path {:parameter-syntax parameter-syntax}))) + (is (= expected (trie/split-path path {:syntax syntax}))) "/olipa/:kerran/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] "/olipa/{kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] @@ -67,9 +67,9 @@ "/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/" (trie/->CatchAll :valtavan.suuri/avaruus)]))) (testing "nil" - (doseq [parameter-syntax [nil]] + (doseq [syntax [nil]] (are [path expected] - (is (= expected (trie/split-path path {:parameter-syntax parameter-syntax}))) + (is (= expected (trie/split-path path {:syntax syntax}))) "/olipa/:kerran/avaruus", ["/olipa/:kerran/avaruus"] "/olipa/{kerran}/avaruus", ["/olipa/{kerran}/avaruus"]