Skip to content

Commit

Permalink
Support three different multi-param syntaxes
Browse files Browse the repository at this point in the history
When params have multiple values, you can now choose the style used to
build a param string. There are three styles available:

by default, a repeating param:

    :a [1 2 3] => "a=1&a=2&a=3"

with :multi-param-style :array, a repeating param with array suffix
(PHP-style):

    :a [1 2 3] => "a[]=1&a[]=2&a[]=3"

with :multi-param-style :indexed, a repeating param with array suffix
 and index (Rails-style):

    :a [1 2 3] => "a[0]=1&a[1]=2&a[2]=3"
  • Loading branch information
joelittlejohn committed Sep 16, 2016
1 parent a62da5b commit cddeb3e
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 17 deletions.
33 changes: 21 additions & 12 deletions src/clj_http/client.clj
Original file line number Diff line number Diff line change
Expand Up @@ -618,28 +618,34 @@
(second found))
"UTF-8"))

(defn generate-query-string-with-encoding [params encoding]
(defn- multi-param-suffix [index multi-param-style]
(case multi-param-style
:indexed (str "[" index "]")
:array "[]"
""))

(defn generate-query-string-with-encoding [params encoding multi-param-style]
(str/join "&"
(mapcat (fn [[k v]]
(if (sequential? v)
(map #(str (util/url-encode (name %1) encoding)
"="
(util/url-encode (str %2) encoding))
(repeat k) v)
(map-indexed #(str (util/url-encode (name k) encoding)
(multi-param-suffix %1 multi-param-style)
"="
(util/url-encode (str %2) encoding)) v)
[(str (util/url-encode (name k) encoding)
"="
(util/url-encode (str v) encoding))]))
params)))

(defn generate-query-string [params & [content-type]]
(defn generate-query-string [params & [content-type multi-param-style]]
(let [encoding (detect-charset content-type)]
(generate-query-string-with-encoding params encoding)))
(generate-query-string-with-encoding params encoding multi-param-style)))

(defn wrap-query-params
"Middleware converting the :query-params option to a querystring on
the request."
[client]
(fn [{:keys [query-params content-type]
(fn [{:keys [query-params content-type multi-param-style]
:or {content-type :x-www-form-urlencoded}
:as req}]
(if query-params
Expand All @@ -651,7 +657,8 @@
new-query-string))
(generate-query-string
query-params
(content-type-value content-type)))))
(content-type-value content-type)
multi-param-style))))
(client req))))

(defn basic-auth-value [basic-auth]
Expand Down Expand Up @@ -737,11 +744,13 @@
:json-opts json-opts})))
(json-encode form-params json-opts))

(defmethod coerce-form-params :default [{:keys [content-type form-params
(defmethod coerce-form-params :default [{:keys [content-type
multi-param-style
form-params
form-param-encoding]}]
(if form-param-encoding
(generate-query-string-with-encoding form-params form-param-encoding)
(generate-query-string form-params (content-type-value content-type))))
(generate-query-string-with-encoding form-params form-param-encoding multi-param-style)
(generate-query-string form-params (content-type-value content-type) multi-param-style)))

(defn wrap-form-params
"Middleware wrapping the submission or form parameters."
Expand Down
42 changes: 38 additions & 4 deletions test/clj_http/test/client_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -616,10 +616,10 @@
(are [in out] (is-applied client/wrap-nested-params
{:query-params in :form-params in}
{:query-params out :form-params out})
{"foo" "bar"} {"foo" "bar"}
{"x" {"y" "z"}} {"x[y]" "z"}
{"a" {"b" {"c" "d"}}} {"a[b][c]" "d"}
{"a" "b", "c" "d"} {"a" "b", "c" "d"}))
{"foo" "bar"} {"foo" "bar"}
{"x" {"y" "z"}} {"x[y]" "z"}
{"a" {"b" {"c" "d"}}} {"a[b][c]" "d"}
{"a" "b", "c" "d"} {"a" "b", "c" "d"}))

(testing "not creating empty param maps"
(is-applied client/wrap-query-params {} {})))
Expand Down Expand Up @@ -841,3 +841,37 @@
(is (= 1 (:major protocol-version)))
(is (= 1 (:minor protocol-version)))
(is (= "OK" (:reason-phrase resp)))))

(deftest ^:integration multi-valued-query-params
(run-server)
(testing "default (repeating) multi-valued query params"
(let [resp (request {:uri "/query-string"
:method :get
:query-params {:a [1 2 3]
:b ["x" "y" "z"]}})
query-string (-> resp :body form-decode-str)]
(is (= 200 (:status resp)))
(is (.contains query-string "a=1&a=2&a=3") query-string)
(is (.contains query-string "b=x&b=y&b=z") query-string)))

(testing "multi-valued query params in indexed-style"
(let [resp (request {:uri "/query-string"
:method :get
:multi-param-style :indexed
:query-params {:a [1 2 3]
:b ["x" "y" "z"]}})
query-string (-> resp :body form-decode-str)]
(is (= 200 (:status resp)))
(is (.contains query-string "a[0]=1&a[1]=2&a[2]=3") query-string)
(is (.contains query-string "b[0]=x&b[1]=y&b[2]=z") query-string)))

(testing "multi-valued query params in array-style"
(let [resp (request {:uri "/query-string"
:method :get
:multi-param-style :array
:query-params {:a [1 2 3]
:b ["x" "y" "z"]}})
query-string (-> resp :body form-decode-str)]
(is (= 200 (:status resp)))
(is (.contains query-string "a[]=1&a[]=2&a[]=3") query-string)
(is (.contains query-string "b[]=x&b[]=y&b[]=z") query-string))))
4 changes: 3 additions & 1 deletion test/clj_http/test/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@
[:propfind "/propfind"]
{:status 200 :body "propfind"}
[:propfind "/propfind-with-body"]
{:status 200 :body (:body req)}))
{:status 200 :body (:body req)}
[:get "/query-string"]
{:status 200 :body (:query-string req)}))

(defn run-server
[]
Expand Down

0 comments on commit cddeb3e

Please sign in to comment.