From 4d48326ef255f15e37586ed855cf31fffcdf667e Mon Sep 17 00:00:00 2001 From: John Christopher Jones Date: Sat, 19 Sep 2020 19:45:17 -0400 Subject: [PATCH] feat: support :multipart options, modeled after clj-http [#26] --- src/babashka/curl.clj | 26 +++++++++++++++++++++++-- test/babashka/curl_test.clj | 38 +++++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/babashka/curl.clj b/src/babashka/curl.clj index 49c9dd5..99fb58b 100644 --- a/src/babashka/curl.clj +++ b/src/babashka/curl.clj @@ -57,6 +57,27 @@ [^String unencoded] (URLEncoder/encode unencoded "UTF-8")) +(defn- refer-file + "If the value is a file, translate into a curl file read." + [v] + (if (file? v) (str "@" (.getPath ^File v)) v)) + +(defn create-multipart-param + [{:keys [name part-name content mime-type] :as part}] + (if (vector? part) + ["--form" (str/join "=" part)] + (let [k (or part-name name) + v (refer-file content) + t (when mime-type (str ";type=" mime-type)) + headers-keys (remove #{:name :part-name :content :mime-type} part)] + ["--form" (str k "=" v t)])) + ) + +(defn- create-multipart-params + "Take a multipart vector of maps and create a seq of curl form parameters." + [multipart] + (apply concat (map create-multipart-param multipart))) + (defn- curl-command [opts] (let [body (:body opts) opts (if body @@ -82,10 +103,11 @@ kvs (seq form-params)] (if kvs (let [[k v] (first kvs) - v (if (file? v) (str "@" (.getPath ^File v)) v) + v (refer-file v) param ["--data" (str (url-encode k) "=" (url-encode v))]] (recur (reduce conj! params* param) (next kvs))) (persistent! params*)))) + multipart-params (create-multipart-params (:multipart opts)) ;; TODO: ;; multipart-params (when-let [multipart (:multipart opts)] ;; (loop [params* (transient []) @@ -138,7 +160,7 @@ stream? (identical? :stream (:as opts))] [(conj (reduce into ["curl" "--silent" "--show-error" "--location" "--dump-header" header-file] [method headers accept-header data-raw in-file in-stream basic-auth - form-params #_multipart-params + form-params multipart-params ;; tested with SSE server, e.g. https://github.com/enkot/SSE-Fake-Server (when stream? ["-N"]) (:raw-args opts)]) diff --git a/test/babashka/curl_test.clj b/test/babashka/curl_test.clj index ce477ac..5d54486 100644 --- a/test/babashka/curl_test.clj +++ b/test/babashka/curl_test.clj @@ -64,8 +64,7 @@ headers (:headers body) content-type (:content-type headers)] (is (= "application/x-www-form-urlencoded" content-type)))) - ;; TODO: - #_(testing "multipart" + (testing "multipart" (testing "posting file" (let [tmp-file (java.io.File/createTempFile "foo" "bar") _ (spit tmp-file "Michiel Borkent") @@ -81,6 +80,8 @@ (is (str/starts-with? content-type "multipart/form-data")) (is (:files body)) (is (str/includes? (-> body :form :filename) "foo")) + #_(is (str/includes? (-> body :form :filename) "filename")) + #_(is (str/includes? (-> body :form :filename) "file2")) (prn body))))) (deftest patch-test @@ -244,3 +245,36 @@ (let [response (curl/get "https://httpstat.us/404" {:throw false})] (is (= 404 (:status response))) (is (zero? (:exit response)))))) + + +(deftest create-multipart-params-test + (testing "nil multipart" + (is (= nil (seq (#'curl/create-multipart-params nil))))) + (testing "string content" + (let [multipart [{:name "foo" :content "bar"}]] + (is (= ["--form" "foo=bar"] + (#'curl/create-multipart-params multipart))))) + (testing "with :mime-type" + (let [multipart [{:name "boo" :content "baz" :mime-type "text/plain"}]] + (is (= ["--form" "boo=baz;type=text/plain"] + (#'curl/create-multipart-params multipart))))) + (testing "with :part-name" + (let [multipart [{:name "boo" :content "baz" :mime-type "text/plain" :part-name "hotdog"}]] + (is (= ["--form" "hotdog=baz;type=text/plain"] + (#'curl/create-multipart-params multipart))))) + (testing "File content" + (let [multipart [{:name "myfile" :content (io/file "README.md")}]] + (is (= ["--form" "myfile=@README.md"] + (#'curl/create-multipart-params multipart))))) + #_(testing "vector part" + (let [multipart [["file4" (io/file "README.md")]]] + (is (= ["--form" "file4=@README.md"] + (#'curl/create-multipart-params multipart))))) + (testing "multiple fields" + (let [multipart [{:name "title" :content "awesome"} + {:name "foo" :part-name "eggplant" :content "zack" :mime-type "text/plain"} + {:name "myfile" :content (io/file "README.md")}]] + (is (= ["--form" "title=awesome" + "--form" "eggplant=zack;type=text/plain" + "--form" "myfile=@README.md"] + (#'curl/create-multipart-params multipart))))))