From 5ef30443efd9715df9e816362b249c4103bcd924 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Tue, 20 Mar 2018 16:30:53 +0200 Subject: [PATCH 1/9] Initial take on IntoString --- modules/reitit-core/src/reitit/core.cljc | 8 ++-- modules/reitit-core/src/reitit/impl.cljc | 55 ++++++++++++++++++++++-- test/cljc/reitit/core_test.cljc | 6 +++ test/cljc/reitit/impl_test.cljc | 28 ++++++++++++ 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index 535353f4..35d2c87b 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -175,7 +175,7 @@ (match nil))) (match-by-name [_ name path-params] (if-let [match (impl/fast-get lookup name)] - (match path-params))))))) + (match (impl/path-params path-params)))))))) (defn lookup-router "Creates a lookup-router from resolved routes and optional @@ -215,7 +215,7 @@ (match nil))) (match-by-name [_ name path-params] (if-let [match (impl/fast-get lookup name)] - (match path-params))))))) + (match (impl/path-params path-params)))))))) (defn segment-router "Creates a special prefix-tree style segment router from resolved routes and optional @@ -255,7 +255,7 @@ (match nil))) (match-by-name [_ name path-params] (if-let [match (impl/fast-get lookup name)] - (match path-params))))))) + (match (impl/path-params path-params)))))))) (defn single-static-path-router "Creates a fast router of 1 static route(s) and optional @@ -290,7 +290,7 @@ match)) (match-by-name [_ name path-params] (if (= n name) - (impl/fast-assoc match :path-params path-params))))))) + (impl/fast-assoc match :path-params (impl/path-params path-params)))))))) (defn mixed-router "Creates two routers: [[lookup-router]] or [[single-static-path-router]] for diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 46d5a3b7..28499e75 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -13,8 +13,10 @@ (ns ^:no-doc reitit.impl (:require [clojure.string :as str] [clojure.set :as set]) - #?(:clj (:import (java.util.regex Pattern) - (java.util HashMap Map)))) + #?(:clj + (:import (java.util.regex Pattern) + (java.util HashMap Map) + (java.net URLEncoder URLDecoder)))) (defn wild? [s] (contains? #{\: \*} (first (str s)))) @@ -135,7 +137,7 @@ (defn throw-on-missing-path-params [template required path-params] (when-not (every? #(contains? path-params %) required) (let [defined (-> path-params keys set) - missing (clojure.set/difference required defined)] + missing (set/difference required defined)] (throw (ex-info (str "missing path-params for route " template " -> " missing) @@ -155,3 +157,50 @@ (defn strip-nils [m] (->> m (remove (comp nil? second)) (into {}))) + +;; +;; Path-parameters, see https://github.com/metosin/reitit/issues/75 +;; + +(defn url-encode [s] + (some-> s + #?(:clj (URLEncoder/encode "UTF-8") + :cljs (js/encodeURIComponent)) + (.replace "+" "%20"))) + +(defn url-decode [s] + #?(:clj (some-> s (URLDecoder/decode "UTF-8")) + :cljs (some-> s (js/decodeURIComponent)))) + +(defprotocol IntoString + (into-string [_])) + +(extend-protocol IntoString + #?(:clj String + :cljs string) + (into-string [this] this) + + #?(:clj clojure.lang.Keyword + :cljs cljs.core.Keyword) + (into-string [this] + (str (namespace this) + (when (namespace this) "/") + (name this))) + + #?(:clj Number + :cljs number) + (into-string [this] (str this)) + + #?(:clj Object + :cljs object) + (into-string [this] (str this))) + +(defn path-params + "shallow transform of the path-param values into strings" + [params] + (persistent! + (reduce-kv + (fn [m k v] + (assoc! m k (url-encode (into-string v)))) + (transient {}) + params))) diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index 83ef54c1..e8144e75 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -29,6 +29,12 @@ :path "/api/ipa/large" :path-params {:size "large"}}) (r/match-by-name router ::beer {:size "large"}))) + (is (= (r/map->Match + {:template "/api/ipa/:size" + :data {:name ::beer} + :path "/api/ipa/large" + :path-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))) diff --git a/test/cljc/reitit/impl_test.cljc b/test/cljc/reitit/impl_test.cljc index 45032008..8ae5839d 100644 --- a/test/cljc/reitit/impl_test.cljc +++ b/test/cljc/reitit/impl_test.cljc @@ -10,3 +10,31 @@ (deftest strip-nils-test (is (= {:a 1, :c false} (impl/strip-nils {:a 1, :b nil, :c false})))) + +(deftest into-string-test + (is (= "1" (impl/into-string 1))) + (is (= "2.2" (impl/into-string 2.2))) + (is (= "kikka" (impl/into-string "kikka"))) + (is (= "kikka" (impl/into-string :kikka))) + (is (= "reitit.impl-test/kikka" (impl/into-string ::kikka)))) + +(deftest url-encode-and-decode-test + (is (= "reitit.impl-test%2Fkikka" (-> ::kikka + impl/into-string + impl/url-encode))) + (is (= "reitit.impl-test/kikka" (-> ::kikka + impl/into-string + impl/url-encode + impl/url-decode)))) + +(deftest path-params-test + (is (= {:n "1" + :d "2.2" + :s "kikka" + :k "kikka" + :qk "reitit.impl-test%2Fkikka"} + (impl/path-params {:n 1 + :d 2.2 + :s "kikka" + :k :kikka + :qk ::kikka})))) From f547576a444f55040de80c169121108d5320e237 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Wed, 21 Mar 2018 07:48:56 +0200 Subject: [PATCH 2/9] Support also booleans, more tests --- modules/reitit-core/src/reitit/impl.cljc | 4 ++++ test/cljc/reitit/impl_test.cljc | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 28499e75..a012488f 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -187,6 +187,10 @@ (when (namespace this) "/") (name this))) + #?(:clj Boolean + :cljs boolean) + (into-string [this] (str this)) + #?(:clj Number :cljs number) (into-string [this] (str this)) diff --git a/test/cljc/reitit/impl_test.cljc b/test/cljc/reitit/impl_test.cljc index 8ae5839d..b822666f 100644 --- a/test/cljc/reitit/impl_test.cljc +++ b/test/cljc/reitit/impl_test.cljc @@ -23,18 +23,32 @@ impl/into-string impl/url-encode))) (is (= "reitit.impl-test/kikka" (-> ::kikka - impl/into-string - impl/url-encode - impl/url-decode)))) + impl/into-string + impl/url-encode + impl/url-decode)))) (deftest path-params-test (is (= {:n "1" + :n1 "-1" + :n2 "1" + :n3 "1" + :n4 "1" + :n5 "1" :d "2.2" + :b "true" :s "kikka" + :u "c2541900-17a7-4353-9024-db8ac258ba4e" :k "kikka" :qk "reitit.impl-test%2Fkikka"} (impl/path-params {:n 1 + :n1 -1 + :n2 (long 1) + :n3 (int 1) + :n4 (short 1) + :n5 (byte 1) :d 2.2 + :b true :s "kikka" + :u #uuid "c2541900-17a7-4353-9024-db8ac258ba4e" :k :kikka :qk ::kikka})))) From f793a224c7c11f6370cc33ca0d6d331f66ded0f1 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Wed, 21 Mar 2018 07:56:29 +0200 Subject: [PATCH 3/9] Update docs --- doc/basics/name_based_routing.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/basics/name_based_routing.md b/doc/basics/name_based_routing.md index 69bbf6af..f428e6a4 100644 --- a/doc/basics/name_based_routing.md +++ b/doc/basics/name_based_routing.md @@ -64,6 +64,17 @@ With provided path-parameters: ; :path-params {:id "1"}} ``` +Path-parameters are automatically coerced into strings, with the help of (currently internal) Protocol `reitit.impl/IntoString`. It supports numbers, booleans, keywords and objects: + +```clj +(r/match-by-name router ::user {:id 1}) +; #Match{:template "/api/user/:id" +; :data {:name :user/user} +; :path "/api/user/1" +; :result nil +; :path-params {:id "1"}} +``` + There is also a exception throwing version: ```clj From 97a00f57f133a08ee91f513a2946f0a2b23d0b86 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Wed, 21 Mar 2018 07:56:50 +0200 Subject: [PATCH 4/9] CHANGELOG --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cc51e35..bb830655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 0.1.1-SNAPSHOT + +### `reitit-core` + +* `match-by-path` encodes parameters into strings using (internal) `reitit.impl/IntoString` protocol. Handles all of: numbers, keywords, booleans, uuids. Fixes [#75](https://github.com/metosin/reitit/issues/75). + +```clj +(require '[reitit.core :as r]) + +(r/match-by-name + (r/router + ["/coffee/:type" ::coffee]) + ::coffee + {:type :luwak}) +;#Match{:template "/coffee/:type", +; :data {:name :user/coffee}, +; :result nil, +; :path-params {:type "luwak"}, +; :path "/coffee/luwa"} +``` + ## 0.1.0 (2018-2-19) * First release From 20c54b1d7be4d02d37fc5704012f8e78e4633130 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Wed, 21 Mar 2018 07:58:06 +0200 Subject: [PATCH 5/9] Changelog for reitit-swagger --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb830655..6470a608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,51 @@ ; :path "/coffee/luwa"} ``` +### `reitit-swagger` + +* New module to produce swagger-docs from routing tree, including `Coercion` definitions. Works with both middleware & interceptors. + +```clj +(require '[reitit.ring :as ring]) +(require '[reitit.swagger :as swagger]) +(require '[reitit.ring.coercion :as rrc]) +(require '[reitit.coercion.spec :as spec]) +(require '[reitit.coercion.schema :as schema]) + +(require '[schema.core :refer [Int]]) + +(ring/ring-handler + (ring/router + ["/api" + {:swagger {:id ::math}} + + ["/swagger.json" + {:get {:no-doc true + :swagger {:info {:title "my-api"}} + :handler swagger/swagger-spec-handler}}] + + ["/spec" {:coercion spec/coercion} + ["/plus" + {:get {:summary "plus" + :parameters {:query {:x int?, :y int?}} + :responses {200 {:body {:total int?}}} + :handler (fn [{{{:keys [x y]} :query} :parameters}] + {:status 200, :body {:total (+ x y)}})}}]] + + ["/schema" {:coercion schema/coercion} + ["/plus" + {:get {:summary "plus" + :parameters {:query {:x Int, :y Int}} + :responses {200 {:body {:total Int}}} + :handler (fn [{{{:keys [x y]} :query} :parameters}] + {:status 200, :body {:total (+ x y)}})}}]]] + + {:data {:middleware [rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware + swagger/swagger-feature]}})) +``` + ## 0.1.0 (2018-2-19) * First release From 01730778e51a892d026b0a2d5929db353753f199 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Wed, 21 Mar 2018 08:02:49 +0200 Subject: [PATCH 6/9] Tune CHANGELOG --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6470a608..abebb0af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### `reitit-core` -* `match-by-path` encodes parameters into strings using (internal) `reitit.impl/IntoString` protocol. Handles all of: numbers, keywords, booleans, uuids. Fixes [#75](https://github.com/metosin/reitit/issues/75). +* `match-by-path` encodes parameters into strings using (internal) `reitit.impl/IntoString` protocol. Handles all of: strings, numbers, keywords, booleans, objects. Fixes [#75](https://github.com/metosin/reitit/issues/75). ```clj (require '[reitit.core :as r]) @@ -16,7 +16,7 @@ ; :data {:name :user/coffee}, ; :result nil, ; :path-params {:type "luwak"}, -; :path "/coffee/luwa"} +; :path "/coffee/luwak"} ``` ### `reitit-swagger` From b4835a7860f85d8926fe535c4786415e03ca7d3a Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Wed, 21 Mar 2018 08:15:28 +0200 Subject: [PATCH 7/9] Polish code --- doc/basics/name_based_routing.md | 2 +- modules/reitit-core/src/reitit/impl.cljc | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/basics/name_based_routing.md b/doc/basics/name_based_routing.md index f428e6a4..3e5c7d8c 100644 --- a/doc/basics/name_based_routing.md +++ b/doc/basics/name_based_routing.md @@ -64,7 +64,7 @@ With provided path-parameters: ; :path-params {:id "1"}} ``` -Path-parameters are automatically coerced into strings, with the help of (currently internal) Protocol `reitit.impl/IntoString`. It supports numbers, booleans, keywords and objects: +Path-parameters are automatically coerced into strings, with the help of (currently internal) Protocol `reitit.impl/IntoString`. It supports strings, numbers, booleans, keywords and objects: ```clj (r/match-by-name router ::user {:id 1}) diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index a012488f..9df30456 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -169,8 +169,8 @@ (.replace "+" "%20"))) (defn url-decode [s] - #?(:clj (some-> s (URLDecoder/decode "UTF-8")) - :cljs (some-> s (js/decodeURIComponent)))) + (some-> s #?(:clj (URLDecoder/decode "UTF-8") + :cljs (js/decodeURIComponent)))) (defprotocol IntoString (into-string [_])) @@ -183,9 +183,8 @@ #?(:clj clojure.lang.Keyword :cljs cljs.core.Keyword) (into-string [this] - (str (namespace this) - (when (namespace this) "/") - (name this))) + (let [ns (namespace this)] + (str ns (if ns "/") (name this)))) #?(:clj Boolean :cljs boolean) From 1c026569d9cd318e67f2a2ea56e05ad554bec6bf Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Wed, 21 Mar 2018 08:18:52 +0200 Subject: [PATCH 8/9] Cleanup tests --- test/cljc/reitit/impl_test.cljc | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/test/cljc/reitit/impl_test.cljc b/test/cljc/reitit/impl_test.cljc index b822666f..3e2e5ff5 100644 --- a/test/cljc/reitit/impl_test.cljc +++ b/test/cljc/reitit/impl_test.cljc @@ -11,21 +11,15 @@ (deftest strip-nils-test (is (= {:a 1, :c false} (impl/strip-nils {:a 1, :b nil, :c false})))) -(deftest into-string-test - (is (= "1" (impl/into-string 1))) - (is (= "2.2" (impl/into-string 2.2))) - (is (= "kikka" (impl/into-string "kikka"))) - (is (= "kikka" (impl/into-string :kikka))) - (is (= "reitit.impl-test/kikka" (impl/into-string ::kikka)))) - (deftest url-encode-and-decode-test (is (= "reitit.impl-test%2Fkikka" (-> ::kikka impl/into-string impl/url-encode))) - (is (= "reitit.impl-test/kikka" (-> ::kikka - impl/into-string - impl/url-encode - impl/url-decode)))) + (is (= ::kikka (-> ::kikka + impl/into-string + impl/url-encode + impl/url-decode + keyword)))) (deftest path-params-test (is (= {:n "1" From 586b02f67d84958e8e692fa52c6009d0bc3bafa8 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Thu, 22 Mar 2018 18:57:53 +0200 Subject: [PATCH 9/9] =?UTF-8?q?Fix=20based=20on=20Miikka=E2=80=99s=20comme?= =?UTF-8?q?nts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/reitit-core/src/reitit/impl.cljc | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 9df30456..9795cf11 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -166,7 +166,7 @@ (some-> s #?(:clj (URLEncoder/encode "UTF-8") :cljs (js/encodeURIComponent)) - (.replace "+" "%20"))) + #?(:clj (.replace "+" "%20")))) (defn url-decode [s] (some-> s #?(:clj (URLDecoder/decode "UTF-8") @@ -201,9 +201,8 @@ (defn path-params "shallow transform of the path-param values into strings" [params] - (persistent! - (reduce-kv - (fn [m k v] - (assoc! m k (url-encode (into-string v)))) - (transient {}) - params))) + (reduce-kv + (fn [m k v] + (assoc m k (url-encode (into-string v)))) + {} + params))