diff --git a/CHANGELOG.md b/CHANGELOG.md index f0c465bf..5aa591cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +## UNRELEASED + +## `reitit-core` + +* `reitit.coercion/coerce!` coerced all parameters found in match, e.g. injecting in `:query-parameters` into `Match` with coerce those too if `:query` coercion is defined. +* `spec-tools.data-spec/maybe` can be used in spec-coercion. + +```clj +(def router + (reitit.core/router + ["/spec" {:coercion reitit.coercion.spec/coercion} + ["/:number/:keyword" {:parameters {:path {:number int? + :keyword keyword?} + :query (ds/maybe {:int int?})}}]] + {:compile reitit.coercion/compile-request-coercers})) + +(-> (reitit.core/match-by-path router "/spec/10/kikka") + (assoc :query-params {:int "10"}) + (reitit.coercion/coerce!)) +; {:path {:number 10, :keyword :kikka} +; :query {:int 10}} +``` + +* `reitit.core/match->path` to create full paths from match, including the query parameters: + +```clj +(require '[reitit.core :as r]) + +(-> (r/router ["/:a/:b" ::route]) + (r/match-by-name! ::route {:a "olipa", :b "kerran"}) + (r/match->path)) +; "/olipa/kerran" + +(-> (r/router ["/:a/:b" ::route]) + (r/match-by-name! ::route {:a "olipa", :b "kerran"}) + (r/match->path {:iso "pöriläinen"})) +; "/olipa/kerran?iso=p%C3%B6ril%C3%A4inen" +``` + ## 0.1.2 (2018-6-6) ### `reitit-core` diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 8389c79e..4fdababa 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -34,7 +34,7 @@ (defrecord ParameterCoercion [in style keywordize? open?]) -(def ^:no-doc ring-parameter-coercion +(def ^:no-doc defaut-parameter-coercion {:query (->ParameterCoercion :query-params :string true true) :body (->ParameterCoercion :body-params :body false false) :form (->ParameterCoercion :form-params :string true true) @@ -73,7 +73,7 @@ (defn request-coercer [coercion type model {:keys [extract-request-format] :or {extract-request-format extract-request-format-default}}] (if coercion - (let [{:keys [keywordize? open? in style]} (ring-parameter-coercion type) + (let [{:keys [keywordize? open? in style]} (defaut-parameter-coercion type) transform (comp (if keywordize? walk/keywordize-keys identity) in) model (if open? (-open-model coercion model) model) coercer (-request-coercer coercion style model)] @@ -155,5 +155,5 @@ coercers under `:result` (provided by [[compile-request-coercers]]. If coercion or parameters are not defined, return `nil`" [match] - (if-let [result (:result match)] - (coerce-request result {:path-params (:path-params match)}))) + (if-let [coercers (:result match)] + (coerce-request coercers match))) diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index b3785447..2a22f1a7 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -129,6 +129,12 @@ (impl/throw-on-missing-path-params (:template match) (:required match) path-params))))) +(defn match->path + ([match] + (match->path match nil)) + ([match query-params] + (some-> match :path (cond-> query-params (str "?" (impl/query-string query-params)))))) + (def default-router-options {:lookup name-lookup :expand expand diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 1c654d36..003f2870 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -196,13 +196,23 @@ #?(:clj Object :cljs object) - (into-string [this] (str this))) + (into-string [this] (str this)) + + nil + (into-string [this])) (defn path-params - "shallow transform of the path-param values into strings" + "shallow transform of the path parameters values into strings" [params] (reduce-kv (fn [m k v] (assoc m k (url-encode (into-string v)))) {} params)) + +(defn query-string + "shallow transform of query parameters into query string" + [params] + (->> params + (map (fn [[k v]] (str (url-encode (into-string k)) "=" (url-encode (into-string v))))) + (str/join "&"))) diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index d24f08c5..18025ae8 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -1,13 +1,14 @@ (ns reitit.coercion.spec (:require [clojure.spec.alpha :as s] [spec-tools.core :as st #?@(:cljs [:refer [Spec]])] - [spec-tools.data-spec :as ds] + [spec-tools.data-spec :as ds #?@(:cljs [:refer [Maybe]])] [spec-tools.transform :as stt] [spec-tools.swagger.core :as swagger] [reitit.coercion :as coercion] [clojure.set :as set]) #?(:clj - (:import (spec_tools.core Spec)))) + (:import (spec_tools.core Spec) + (spec_tools.data_spec Maybe)))) (def string-transformer (st/type-transformer @@ -48,6 +49,10 @@ (into-spec [this name] (ds/spec (ensure-name name) this)) + Maybe + (into-spec [this name] + (ds/spec (ensure-name name) this)) + Spec (into-spec [this _] this) diff --git a/test/cljc/reitit/coercion_test.cljc b/test/cljc/reitit/coercion_test.cljc index ef067497..b286b286 100644 --- a/test/cljc/reitit/coercion_test.cljc +++ b/test/cljc/reitit/coercion_test.cljc @@ -1,23 +1,26 @@ (ns reitit.coercion-test (:require [clojure.test :refer [deftest testing is]] [schema.core :as s] + [spec-tools.data-spec :as ds] [reitit.core :as r] [reitit.coercion :as coercion] - [reitit.coercion.spec :as spec] - [reitit.coercion.schema :as schema]) + [reitit.coercion.spec] + [reitit.coercion.schema]) #?(:clj (:import (clojure.lang ExceptionInfo)))) -(deftest spec-coercion-test +(deftest coercion-test (let [r (r/router - [["/schema" {:coercion schema/coercion} + [["/schema" {:coercion reitit.coercion.schema/coercion} ["/:number/:keyword" {:name ::user :parameters {:path {:number s/Int - :keyword s/Keyword}}}]] - ["/spec" {:coercion spec/coercion} + :keyword s/Keyword} + :query (s/maybe {:int s/Int})}}]] + ["/spec" {:coercion reitit.coercion.spec/coercion} ["/:number/:keyword" {:name ::user :parameters {:path {:number int? - :keyword keyword?}}}]] + :keyword keyword?} + :query (ds/maybe {:int int?})}}]] ["/none" ["/:number/:keyword" {:name ::user :parameters {:path {:number int? @@ -27,8 +30,11 @@ (testing "schema-coercion" (testing "succeeds" (let [m (r/match-by-path r "/schema/1/abba")] - (is (= {:path {:keyword :abba, :number 1}} - (coercion/coerce! m))))) + (is (= {:path {:keyword :abba, :number 1}, :query nil} + (coercion/coerce! m)))) + (let [m (r/match-by-path r "/schema/1/abba")] + (is (= {:path {:keyword :abba, :number 1}, :query {:int 10}} + (coercion/coerce! (assoc m :query-params {:int "10"})))))) (testing "throws with invalid input" (let [m (r/match-by-path r "/schema/kikka/abba")] (is (thrown? ExceptionInfo (coercion/coerce! m)))))) @@ -36,8 +42,11 @@ (testing "spec-coercion" (testing "succeeds" (let [m (r/match-by-path r "/spec/1/abba")] - (is (= {:path {:keyword :abba, :number 1}} - (coercion/coerce! m))))) + (is (= {:path {:keyword :abba, :number 1}, :query nil} + (coercion/coerce! m)))) + (let [m (r/match-by-path r "/schema/1/abba")] + (is (= {:path {:keyword :abba, :number 1}, :query {:int 10}} + (coercion/coerce! (assoc m :query-params {:int "10"})))))) (testing "throws with invalid input" (let [m (r/match-by-path r "/spec/kikka/abba")] (is (thrown? ExceptionInfo (coercion/coerce! m)))))) diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index f8233888..f1a27584 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -246,3 +246,14 @@ [["/a"] ["/a"]])))) (testing "can be configured to ignore" (is (not (nil? (r/router [["/a"] ["/a"]] {:conflicts (constantly nil)}))))))) + +(deftest match->path-test + (let [router (r/router ["/:a/:b" ::route])] + (is (= "/olipa/kerran" + (-> router + (r/match-by-name! ::route {:a "olipa", :b "kerran"}) + (r/match->path)))) + (is (= "/olipa/kerran?iso=p%C3%B6ril%C3%A4inen" + (-> router + (r/match-by-name! ::route {:a "olipa", :b "kerran"}) + (r/match->path {:iso "pöriläinen"})))))) diff --git a/test/cljc/reitit/impl_test.cljc b/test/cljc/reitit/impl_test.cljc index 3e2e5ff5..1d437ed8 100644 --- a/test/cljc/reitit/impl_test.cljc +++ b/test/cljc/reitit/impl_test.cljc @@ -33,7 +33,8 @@ :s "kikka" :u "c2541900-17a7-4353-9024-db8ac258ba4e" :k "kikka" - :qk "reitit.impl-test%2Fkikka"} + :qk "reitit.impl-test%2Fkikka" + :nil nil} (impl/path-params {:n 1 :n1 -1 :n2 (long 1) @@ -45,4 +46,21 @@ :s "kikka" :u #uuid "c2541900-17a7-4353-9024-db8ac258ba4e" :k :kikka - :qk ::kikka})))) + :qk ::kikka + :nil nil})))) + +(deftest query-params-test + (are [x y] + (= (impl/query-string x) y) + {:a "b"} "a=b" + {"a" "b"} "a=b" + {:a 1} "a=1" + {:a nil} "a=" + {:a :b :c "d"} "a=b&c=d" + {:a "b c"} "a=b%20c")) + +; TODO: support seq values? +;{:a ["b" "c"]} "a=b&a=c" +;{:a ["c" "b"]} "a=c&a=b" +;{:a (seq [1 2])} "a=1&a=2" +;{:a #{"c" "b"}} "a=b&a=c" diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index eea50d6f..ea767457 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -5,9 +5,9 @@ [reitit.ring.coercion :as rrc] [reitit.coercion.spec :as spec] [reitit.coercion.schema :as schema] - #?(:clj - [muuntaja.middleware]) - [jsonista.core :as j]) + #?@(:clj [ + [muuntaja.middleware] + [jsonista.core :as j]])) #?(:clj (:import (clojure.lang ExceptionInfo) (java.io ByteArrayInputStream))))