Merge pull request #104 from metosin/CoerceAllParametes

Coerce all parameters
This commit is contained in:
Tommi Reiman 2018-06-24 11:37:43 +03:00 committed by GitHub
commit 5bc7b09e19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 122 additions and 24 deletions

View file

@ -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) ## 0.1.2 (2018-6-6)
### `reitit-core` ### `reitit-core`

View file

@ -34,7 +34,7 @@
(defrecord ParameterCoercion [in style keywordize? open?]) (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) {:query (->ParameterCoercion :query-params :string true true)
:body (->ParameterCoercion :body-params :body false false) :body (->ParameterCoercion :body-params :body false false)
:form (->ParameterCoercion :form-params :string true true) :form (->ParameterCoercion :form-params :string true true)
@ -73,7 +73,7 @@
(defn request-coercer [coercion type model {:keys [extract-request-format] (defn request-coercer [coercion type model {:keys [extract-request-format]
:or {extract-request-format extract-request-format-default}}] :or {extract-request-format extract-request-format-default}}]
(if coercion (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) transform (comp (if keywordize? walk/keywordize-keys identity) in)
model (if open? (-open-model coercion model) model) model (if open? (-open-model coercion model) model)
coercer (-request-coercer coercion style model)] coercer (-request-coercer coercion style model)]
@ -155,5 +155,5 @@
coercers under `:result` (provided by [[compile-request-coercers]]. coercers under `:result` (provided by [[compile-request-coercers]].
If coercion or parameters are not defined, return `nil`" If coercion or parameters are not defined, return `nil`"
[match] [match]
(if-let [result (:result match)] (if-let [coercers (:result match)]
(coerce-request result {:path-params (:path-params match)}))) (coerce-request coercers match)))

View file

@ -129,6 +129,12 @@
(impl/throw-on-missing-path-params (impl/throw-on-missing-path-params
(:template match) (:required match) 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 (def default-router-options
{:lookup name-lookup {:lookup name-lookup
:expand expand :expand expand

View file

@ -196,13 +196,23 @@
#?(:clj Object #?(:clj Object
:cljs object) :cljs object)
(into-string [this] (str this))) (into-string [this] (str this))
nil
(into-string [this]))
(defn path-params (defn path-params
"shallow transform of the path-param values into strings" "shallow transform of the path parameters values into strings"
[params] [params]
(reduce-kv (reduce-kv
(fn [m k v] (fn [m k v]
(assoc m k (url-encode (into-string v)))) (assoc m k (url-encode (into-string v))))
{} {}
params)) 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 "&")))

View file

@ -1,13 +1,14 @@
(ns reitit.coercion.spec (ns reitit.coercion.spec
(:require [clojure.spec.alpha :as s] (:require [clojure.spec.alpha :as s]
[spec-tools.core :as st #?@(:cljs [:refer [Spec]])] [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.transform :as stt]
[spec-tools.swagger.core :as swagger] [spec-tools.swagger.core :as swagger]
[reitit.coercion :as coercion] [reitit.coercion :as coercion]
[clojure.set :as set]) [clojure.set :as set])
#?(:clj #?(:clj
(:import (spec_tools.core Spec)))) (:import (spec_tools.core Spec)
(spec_tools.data_spec Maybe))))
(def string-transformer (def string-transformer
(st/type-transformer (st/type-transformer
@ -48,6 +49,10 @@
(into-spec [this name] (into-spec [this name]
(ds/spec (ensure-name name) this)) (ds/spec (ensure-name name) this))
Maybe
(into-spec [this name]
(ds/spec (ensure-name name) this))
Spec Spec
(into-spec [this _] this) (into-spec [this _] this)

View file

@ -1,23 +1,26 @@
(ns reitit.coercion-test (ns reitit.coercion-test
(:require [clojure.test :refer [deftest testing is]] (:require [clojure.test :refer [deftest testing is]]
[schema.core :as s] [schema.core :as s]
[spec-tools.data-spec :as ds]
[reitit.core :as r] [reitit.core :as r]
[reitit.coercion :as coercion] [reitit.coercion :as coercion]
[reitit.coercion.spec :as spec] [reitit.coercion.spec]
[reitit.coercion.schema :as schema]) [reitit.coercion.schema])
#?(:clj #?(:clj
(:import (clojure.lang ExceptionInfo)))) (:import (clojure.lang ExceptionInfo))))
(deftest spec-coercion-test (deftest coercion-test
(let [r (r/router (let [r (r/router
[["/schema" {:coercion schema/coercion} [["/schema" {:coercion reitit.coercion.schema/coercion}
["/:number/:keyword" {:name ::user ["/:number/:keyword" {:name ::user
:parameters {:path {:number s/Int :parameters {:path {:number s/Int
:keyword s/Keyword}}}]] :keyword s/Keyword}
["/spec" {:coercion spec/coercion} :query (s/maybe {:int s/Int})}}]]
["/spec" {:coercion reitit.coercion.spec/coercion}
["/:number/:keyword" {:name ::user ["/:number/:keyword" {:name ::user
:parameters {:path {:number int? :parameters {:path {:number int?
:keyword keyword?}}}]] :keyword keyword?}
:query (ds/maybe {:int int?})}}]]
["/none" ["/none"
["/:number/:keyword" {:name ::user ["/:number/:keyword" {:name ::user
:parameters {:path {:number int? :parameters {:path {:number int?
@ -27,8 +30,11 @@
(testing "schema-coercion" (testing "schema-coercion"
(testing "succeeds" (testing "succeeds"
(let [m (r/match-by-path r "/schema/1/abba")] (let [m (r/match-by-path r "/schema/1/abba")]
(is (= {:path {:keyword :abba, :number 1}} (is (= {:path {:keyword :abba, :number 1}, :query nil}
(coercion/coerce! m))))) (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" (testing "throws with invalid input"
(let [m (r/match-by-path r "/schema/kikka/abba")] (let [m (r/match-by-path r "/schema/kikka/abba")]
(is (thrown? ExceptionInfo (coercion/coerce! m)))))) (is (thrown? ExceptionInfo (coercion/coerce! m))))))
@ -36,8 +42,11 @@
(testing "spec-coercion" (testing "spec-coercion"
(testing "succeeds" (testing "succeeds"
(let [m (r/match-by-path r "/spec/1/abba")] (let [m (r/match-by-path r "/spec/1/abba")]
(is (= {:path {:keyword :abba, :number 1}} (is (= {:path {:keyword :abba, :number 1}, :query nil}
(coercion/coerce! m))))) (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" (testing "throws with invalid input"
(let [m (r/match-by-path r "/spec/kikka/abba")] (let [m (r/match-by-path r "/spec/kikka/abba")]
(is (thrown? ExceptionInfo (coercion/coerce! m)))))) (is (thrown? ExceptionInfo (coercion/coerce! m))))))

View file

@ -246,3 +246,14 @@
[["/a"] ["/a"]])))) [["/a"] ["/a"]]))))
(testing "can be configured to ignore" (testing "can be configured to ignore"
(is (not (nil? (r/router [["/a"] ["/a"]] {:conflicts (constantly nil)}))))))) (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"}))))))

View file

@ -33,7 +33,8 @@
:s "kikka" :s "kikka"
:u "c2541900-17a7-4353-9024-db8ac258ba4e" :u "c2541900-17a7-4353-9024-db8ac258ba4e"
:k "kikka" :k "kikka"
:qk "reitit.impl-test%2Fkikka"} :qk "reitit.impl-test%2Fkikka"
:nil nil}
(impl/path-params {:n 1 (impl/path-params {:n 1
:n1 -1 :n1 -1
:n2 (long 1) :n2 (long 1)
@ -45,4 +46,21 @@
:s "kikka" :s "kikka"
:u #uuid "c2541900-17a7-4353-9024-db8ac258ba4e" :u #uuid "c2541900-17a7-4353-9024-db8ac258ba4e"
:k :kikka :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"

View file

@ -5,9 +5,9 @@
[reitit.ring.coercion :as rrc] [reitit.ring.coercion :as rrc]
[reitit.coercion.spec :as spec] [reitit.coercion.spec :as spec]
[reitit.coercion.schema :as schema] [reitit.coercion.schema :as schema]
#?(:clj #?@(:clj [
[muuntaja.middleware]) [muuntaja.middleware]
[jsonista.core :as j]) [jsonista.core :as j]]))
#?(:clj #?(:clj
(:import (clojure.lang ExceptionInfo) (:import (clojure.lang ExceptionInfo)
(java.io ByteArrayInputStream)))) (java.io ByteArrayInputStream))))