Skip to content

Commit 6294806

Browse files
authored
Merge pull request #46 from bobotu/master
Add support for async handlers
2 parents 46f313a + 3d01de0 commit 6294806

File tree

2 files changed

+222
-25
lines changed

2 files changed

+222
-25
lines changed

src/ring/middleware/json.clj

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424
:headers {"Content-Type" "text/plain"}
2525
:body "Malformed JSON in request body."})
2626

27+
(defn json-body-request
28+
"Parse a JSON request body and assoc it back into the :body key. Returns nil
29+
if the JSON is malformed. See: wrap-json-body."
30+
[request {:keys [keywords? bigdecimals?]}]
31+
(if-let [[valid? json] (read-json request {:keywords? keywords? :bigdecimals? bigdecimals?})]
32+
(if valid? (assoc request :body json))
33+
request))
34+
2735
(defn wrap-json-body
2836
"Middleware that parses the body of JSON request maps, and replaces the :body
2937
key with the parsed data structure. Requests without a JSON content type are
@@ -35,15 +43,18 @@
3543
:bigdecimals? - true if BigDecimals should be used instead of Doubles
3644
:malformed-response - a response map to return when the JSON is malformed"
3745
{:arglists '([handler] [handler options])}
38-
[handler & [{:keys [keywords? bigdecimals? malformed-response]
39-
:or {malformed-response default-malformed-response}}]]
40-
(fn [request]
41-
(if-let [[valid? json]
42-
(read-json request {:keywords? keywords? :bigdecimals? bigdecimals?})]
43-
(if valid?
44-
(handler (assoc request :body json))
45-
malformed-response)
46-
(handler request))))
46+
[handler & [{:keys [malformed-response]
47+
:or {malformed-response default-malformed-response}
48+
:as options}]]
49+
(fn
50+
([request]
51+
(if-let [request (json-body-request request options)]
52+
(handler request)
53+
malformed-response))
54+
([request respond raise]
55+
(if-let [request (json-body-request request options)]
56+
(handler request respond raise)
57+
(respond malformed-response)))))
4758

4859
(defn- assoc-json-params [request json]
4960
(if (map? json)
@@ -52,6 +63,15 @@
5263
(update-in [:params] merge json))
5364
request))
5465

66+
(defn json-params-request
67+
"Parse the body of JSON requests into a map of parameters, which are added
68+
to the request map on the :json-params and :params keys. Returns nil if the
69+
JSON is malformed. See: wrap-json-params."
70+
[request {:keys [bigdecimals?]}]
71+
(if-let [[valid? json] (read-json request {:bigdecimals? bigdecimals?})]
72+
(if valid? (assoc-json-params request json))
73+
request))
74+
5575
(defn wrap-json-params
5676
"Middleware that parses the body of JSON requests into a map of parameters,
5777
which are added to the request map on the :json-params and :params keys.
@@ -64,14 +84,29 @@
6484
Use the standard Ring middleware, ring.middleware.keyword-params, to
6585
convert the parameters into keywords."
6686
{:arglists '([handler] [handler options])}
67-
[handler & [{:keys [bigdecimals? malformed-response]
68-
:or {malformed-response default-malformed-response}}]]
69-
(fn [request]
70-
(if-let [[valid? json] (read-json request {:bigdecimals? bigdecimals?})]
71-
(if valid?
72-
(handler (assoc-json-params request json))
73-
malformed-response)
74-
(handler request))))
87+
[handler & [{:keys [malformed-response]
88+
:or {malformed-response default-malformed-response}
89+
:as options}]]
90+
(fn
91+
([request]
92+
(if-let [request (json-params-request request options)]
93+
(handler request)
94+
malformed-response))
95+
([request respond raise]
96+
(if-let [request (json-params-request request options)]
97+
(handler request respond raise)
98+
(respond malformed-response)))))
99+
100+
(defn json-response
101+
"Converts responses with a map or a vector for a body into a JSON response.
102+
See: wrap-json-response."
103+
[response options]
104+
(if (coll? (:body response))
105+
(let [json-resp (update-in response [:body] json/generate-string options)]
106+
(if (contains? (:headers response) "Content-Type")
107+
json-resp
108+
(content-type json-resp "application/json; charset=utf-8")))
109+
response))
75110

76111
(defn wrap-json-response
77112
"Middleware that converts responses with a map or a vector for a body into a
@@ -83,11 +118,8 @@
83118
:escape-non-ascii - true if non-ASCII characters should be escaped with \\u"
84119
{:arglists '([handler] [handler options])}
85120
[handler & [{:as options}]]
86-
(fn [request]
87-
(let [response (handler request)]
88-
(if (coll? (:body response))
89-
(let [json-response (update-in response [:body] json/generate-string options)]
90-
(if (contains? (:headers response) "Content-Type")
91-
json-response
92-
(content-type json-response "application/json; charset=utf-8")))
93-
response))))
121+
(fn
122+
([request]
123+
(json-response (handler request) options))
124+
([request respond raise]
125+
(handler request (fn [response] (respond (json-response response options))) raise))))

test/ring/middleware/test/json.clj

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,72 @@
7171
(let [response ((wrap-json-body handler {:bigdecimals? false}) {})]
7272
(is (= (get-in response [:body :bigdecimals]) true)))))))
7373

74+
(deftest test-json-body-cps
75+
(let [identity (fn [request respond _] (respond request))]
76+
(let [handler (wrap-json-body identity)]
77+
(testing "xml body"
78+
(let [request {:headers {"content-type" "application/xml"}
79+
:body (string-input-stream "<xml></xml>")}
80+
response (promise)
81+
exception (promise)]
82+
(handler request response exception)
83+
(is (= "<xml></xml>" (slurp (:body @response))))
84+
(is (not (realized? exception)))))
85+
86+
(testing "json body"
87+
(let [request {:headers {"content-type" "application/json; charset=UTF-8"}
88+
:body (string-input-stream "{\"foo\": \"bar\"}")}
89+
response (promise)
90+
exception (promise)]
91+
(handler request response exception)
92+
(is (= {"foo" "bar"} (:body @response)))
93+
(is (not (realized? exception)))))
94+
95+
(testing "malformed json"
96+
(let [request {:headers {"content-type" "application/json; charset=UTF-8"}
97+
:body (string-input-stream "{\"foo\": \"bar\"")}
98+
response (promise)
99+
exception (promise)]
100+
(handler request response exception)
101+
(is (= @response
102+
{:status 400
103+
:headers {"Content-Type" "text/plain"}
104+
:body "Malformed JSON in request body."}))
105+
(is (not (realized? exception))))))
106+
107+
(let [handler (wrap-json-body identity {:keywords? true})]
108+
(testing "keyword keys"
109+
(let [request {:headers {"content-type" "application/json"}
110+
:body (string-input-stream "{\"foo\": \"bar\"}")}
111+
response (promise)
112+
exception (promise)]
113+
(handler request response exception)
114+
(is (= {:foo "bar"} (:body @response)))
115+
(is (not (realized? exception))))))
116+
117+
(testing "custom malformed json"
118+
(let [malformed {:status 400
119+
:headers {"Content-Type" "text/html"}
120+
:body "<b>Your JSON is wrong!</b>"}
121+
handler (wrap-json-body identity {:malformed-response malformed})
122+
request {:headers {"content-type" "application/json"}
123+
:body (string-input-stream "{\"foo\": \"bar\"")}
124+
response (promise)
125+
exception (promise)]
126+
(handler request response exception)
127+
(is (= @response malformed))
128+
(is (not (realized? exception))))))
129+
130+
(testing "don't overwrite bigdecimal binding"
131+
(let [handler (fn [_ respond _] (respond {:status 200 :headers {} :body {:bigdecimals cheshire.parse/*use-bigdecimals?*}}) )]
132+
(binding [cheshire.parse/*use-bigdecimals?* false]
133+
(let [response (promise)]
134+
((wrap-json-body handler {:bigdecimals? true}) {} response (promise))
135+
(is (= (get-in @response [:body :bigdecimals]) false))))
136+
(binding [cheshire.parse/*use-bigdecimals?* true]
137+
(let [response (promise)]
138+
((wrap-json-body handler {:bigdecimals? false}) {} response (promise))
139+
(is (= (get-in @response [:body :bigdecimals]) true)))))))
74140

75141
(deftest test-json-params
76142
(let [handler (wrap-json-params identity)]
@@ -151,6 +217,68 @@
151217
(let [response ((wrap-json-params handler {:bigdecimals? false}) {})]
152218
(is (= (get-in response [:body :bigdecimals]) true)))))))
153219

220+
(deftest test-json-params-cps
221+
(let [identity (fn [request respond _] (respond request))]
222+
(let [handler (wrap-json-params identity)]
223+
(testing "xml body"
224+
(let [request {:headers {"content-type" "application/xml"}
225+
:body (string-input-stream "<xml></xml>")
226+
:params {"id" 3}}
227+
response (promise)
228+
exception (promise)]
229+
(handler request response exception)
230+
(is (= "<xml></xml>" (slurp (:body @response))))
231+
(is (= {"id" 3} (:params @response)))
232+
(is (nil? (:json-params @response)))
233+
(is (not (realized? exception)))))
234+
235+
(testing "json body"
236+
(let [request {:headers {"content-type" "application/json; charset=UTF-8"}
237+
:body (string-input-stream "{\"foo\": \"bar\"}")
238+
:params {"id" 3}}
239+
response (promise)
240+
exception (promise)]
241+
(handler request response exception)
242+
(is (= {"id" 3, "foo" "bar"} (:params @response)))
243+
(is (= {"foo" "bar"} (:json-params @response)))
244+
(is (not (realized? exception)))))
245+
246+
(testing "malformed json"
247+
(let [request {:headers {"content-type" "application/json; charset=UTF-8"}
248+
:body (string-input-stream "{\"foo\": \"bar\"")}
249+
response (promise)
250+
exception (promise)]
251+
(handler request response exception)
252+
(is (= @response
253+
{:status 400
254+
:headers {"Content-Type" "text/plain"}
255+
:body "Malformed JSON in request body."}))
256+
(is (not (realized? exception))))))
257+
258+
(testing "custom malformed json"
259+
(let [malformed {:status 400
260+
:headers {"Content-Type" "text/html"}
261+
:body "<b>Your JSON is wrong!</b>"}
262+
handler (wrap-json-params identity {:malformed-response malformed})
263+
request {:headers {"content-type" "application/json"}
264+
:body (string-input-stream "{\"foo\": \"bar\"")}
265+
response (promise)
266+
exception (promise)]
267+
(handler request response exception)
268+
(is (= @response malformed))
269+
(is (not (realized? exception)))))
270+
271+
(testing "don't overwrite bigdecimal binding"
272+
(let [handler (fn [_ respond _] (respond {:status 200 :headers {} :body {:bigdecimals cheshire.parse/*use-bigdecimals?*}}) )]
273+
(binding [cheshire.parse/*use-bigdecimals?* false]
274+
(let [response (promise)]
275+
((wrap-json-params handler {:bigdecimals? true}) {} response (promise))
276+
(is (= (get-in @response [:body :bigdecimals]) false))))
277+
(binding [cheshire.parse/*use-bigdecimals?* true]
278+
(let [response (promise)]
279+
((wrap-json-params handler {:bigdecimals? false}) {} response (promise))
280+
(is (= (get-in @response [:body :bigdecimals]) true))))))))
281+
154282
(deftest test-json-response
155283
(testing "map body"
156284
(let [handler (constantly {:status 200 :headers {} :body {:foo "bar"}})
@@ -194,3 +322,40 @@
194322
response ((wrap-json-response handler) {})]
195323
(is (= (get-in response [:headers "Content-Type"]) "application/json; some-param=some-value"))
196324
(is (= (:body response) "{\"foo\":\"bar\"}")))))
325+
326+
(deftest test-json-response-cps
327+
(testing "map body"
328+
(let [handler (fn [_ respond _] (respond {:status 200 :headers {} :body {:foo "bar"}}))
329+
response (promise)
330+
exception (promise)]
331+
((wrap-json-response handler) {} response exception)
332+
(is (= (get-in @response [:headers "Content-Type"]) "application/json; charset=utf-8"))
333+
(is (= (:body @response) "{\"foo\":\"bar\"}"))
334+
(is (not (realized? exception)))))
335+
336+
(testing "string body"
337+
(let [handler (fn [_ respond _] (respond {:status 200 :headers {} :body "foobar"}))
338+
response (promise)
339+
exception (promise)]
340+
((wrap-json-response handler) {} response exception)
341+
(is (= (:headers @response) {}))
342+
(is (= (:body @response) "foobar"))
343+
(is (not (realized? exception)))))
344+
345+
(testing "JSON options"
346+
(let [handler (fn [_ respond _] (respond {:status 200 :headers {} :body {:foo "bar" :baz "quz"}}))
347+
response (promise)
348+
exception (promise)]
349+
((wrap-json-response handler {:pretty true}) {} response exception)
350+
(is (or (= (:body @response) "{\n \"foo\" : \"bar\",\n \"baz\" : \"quz\"\n}")
351+
(= (:body @response) "{\n \"baz\" : \"quz\",\n \"foo\" : \"bar\"\n}")))
352+
(is (not (realized? exception)))))
353+
354+
(testing "don’t overwrite Content-Type if already set"
355+
(let [handler (fn [_ respond _] (respond {:status 200 :headers {"Content-Type" "application/json; some-param=some-value"} :body {:foo "bar"}}))
356+
response (promise)
357+
exception (promise)]
358+
((wrap-json-response handler) {} response exception)
359+
(is (= (get-in @response [:headers "Content-Type"]) "application/json; some-param=some-value"))
360+
(is (= (:body @response) "{\"foo\":\"bar\"}"))
361+
(is (not (realized? exception))))))

0 commit comments

Comments
 (0)