diff --git a/src/ring/mock/request.clj b/src/ring/mock/request.clj index 1d2eff8..c13b04a 100644 --- a/src/ring/mock/request.clj +++ b/src/ring/mock/request.clj @@ -4,8 +4,10 @@ [clojure.string :as string] [ring.util.codec :as codec] [ring.util.mime-type :as mime]) - (:import [java.io ByteArrayInputStream ByteArrayOutputStream File] + (:import [java.io ByteArrayInputStream ByteArrayOutputStream File InputStream] + [java.net URI] [java.nio.charset Charset] + [java.util Map] [org.apache.hc.core5.http ContentType HttpEntity] [org.apache.hc.client5.http.entity.mime MultipartEntityBuilder])) @@ -75,9 +77,9 @@ (defmethod body (class (byte-array 0)) [request bytes] (-> request (content-length (count bytes)) - (assoc :body (java.io.ByteArrayInputStream. bytes)))) + (assoc :body (ByteArrayInputStream. bytes)))) -(defmethod body java.util.Map [request params] +(defmethod body Map [request params] (-> request (content-type "application/x-www-form-urlencoded") (body (encode-params params)))) @@ -99,11 +101,30 @@ (defn- file? [f] (instance? File f)) -(defn- add-multipart-part [builder k v] +(defn- str->bytes ^bytes [^String s] + (.getBytes s ^Charset default-charset)) + +(defn- add-binary-body + [^MultipartEntityBuilder builder key value ^ContentType mimetype + ^String filename] + (let [k (name key)] + (cond + (string? value) + (.addBinaryBody builder k (str->bytes value) mimetype filename) + (bytes? value) + (.addBinaryBody builder k ^bytes value mimetype filename) + (file? value) + (.addBinaryBody builder k ^File value mimetype filename) + (instance? InputStream value) + (.addBinaryBody builder k ^InputStream value mimetype filename) + :else + (throw (IllegalArgumentException. + (str "Cannot encode a value of type " (type value) + " as a multipart body.")))))) + +(defn- add-multipart-part [^MultipartEntityBuilder builder k v] (let [param (if (map? v) v {:value v}) - value (if (string? (:value param)) - (.getBytes (:value param) default-charset) - (:value param)) + value (if (map? v) (:value v) v) mimetype (ContentType/parse (or (:content-type param) (when (file? value) @@ -113,11 +134,11 @@ "application/octet-stream"))) filename (or (:filename param) (when (file? value) (.getName ^File value)))] - (.addBinaryBody builder (name k) value mimetype filename))) + (add-binary-body builder (name k) value mimetype filename))) (defn- multipart-entity ^HttpEntity [params] (let [builder (MultipartEntityBuilder/create)] - (.setCharset builder default-charset) + (.setCharset builder ^Charset default-charset) (doseq [[k v] params] (add-multipart-part builder k v)) (.build builder))) @@ -157,7 +178,7 @@ ([method uri] (request method uri nil)) ([method uri params] - (let [uri (java.net.URI. uri) + (let [uri (URI. uri) scheme (keyword (or (.getScheme uri) "http")) host (or (.getHost uri) "localhost") port (when (not= (.getPort uri) -1) (.getPort uri)) diff --git a/test/ring/mock/request_test.clj b/test/ring/mock/request_test.clj index 5f0ca15..d9e781b 100644 --- a/test/ring/mock/request_test.clj +++ b/test/ring/mock/request_test.clj @@ -1,9 +1,11 @@ (ns ring.mock.request-test (:require [clojure.java.io :as io] + [clojure.spec.alpha :as s] [clojure.test :refer [deftest is testing]] [ring.mock.request :refer [body content-length content-type cookie header json-body multipart-body - query-string request]])) + query-string request]]) + (:import [java.io InputStream])) (deftest test-request (testing "relative uri" @@ -130,19 +132,19 @@ (deftest test-body (testing "string body" (let [resp (body {} "Hello World")] - (is (instance? java.io.InputStream (:body resp))) + (is (instance? InputStream (:body resp))) (is (= (slurp (:body resp)) "Hello World")) (is (= (:content-length resp) 11)))) (testing "map body" (let [resp (body {} (array-map :foo "bar" :fi ["fi" "fo" "fum"]))] - (is (instance? java.io.InputStream (:body resp))) + (is (instance? InputStream (:body resp))) (is (= (slurp (:body resp)) "foo=bar&fi=fi&fi=fo&fi=fum")) (is (= (:content-length resp) 26)) (is (= (:content-type resp) "application/x-www-form-urlencoded")))) (testing "bytes body" (let [resp (body {} (.getBytes "foo"))] - (is (instance? java.io.InputStream (:body resp))) + (is (instance? InputStream (:body resp))) (is (= (slurp (:body resp)) "foo")) (is (= (:content-length resp) 3))))) @@ -150,7 +152,7 @@ (testing "json body" (let [resp (json-body {} {:baz ["qu" "qi" "qo"]})] (is (= (:content-type resp) "application/json")) - (is (instance? java.io.InputStream (:body resp))) + (is (instance? InputStream (:body resp))) (is (= (slurp (:body resp)) "{\"baz\":[\"qu\",\"qi\",\"qo\"]}")) (is (= (:content-length resp) 24))))) @@ -229,7 +231,16 @@ "filename=\"test.html\"\r\n" "Content-Type: text/html\r\n\r\n" "a\n\r\n" - "--" boundary "--\r\n")))))) + "--" boundary "--\r\n"))))) + (testing "not supported type" + (try + (multipart-body {} {:foo :keyword + :bar {:value :keyword + :content-type "text/html" + :filename "test.html"}}) + (catch Exception e + (let [expected-message "Cannot encode a value of type class clojure.lang.Keyword as a multipart body."] + (is (= expected-message (.getMessage ^Exception e)))))))) (defmacro when-clojure-spec [& body]