-
Notifications
You must be signed in to change notification settings - Fork 0
/
json.clj
175 lines (149 loc) · 6.44 KB
/
json.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
(ns ring.middleware.json
"Ring middleware for parsing JSON requests and generating JSON responses."
{:author "James Reeves"
:contributors "Modified by Carlos da Cunha Fontes to work with Babashka"
:url "https://github.com/ring-clojure/ring-json"
:license {:name "Distributed under the MIT License, the same as Ring."}}
(:require [cheshire.core :as json])
(:import [java.io InputStream]))
(def ^{:doc "HTTP token: 1*<any CHAR except CTLs or tspecials>. See RFC2068"}
re-token
#"[!#$%&'*\-+.0-9A-Z\^_`a-z\|~]+")
(def ^{:doc "HTTP quoted-string: <\"> *<any TEXT except \"> <\">. See RFC2068."}
re-quoted
#"\"((?:\\\"|[^\"])*)\"")
(def ^{:doc "HTTP value: token | quoted-string. See RFC2109"}
re-value
(str "(" re-token ")|" re-quoted))
(def ^{:doc "Pattern for pulling the charset out of the content-type header"}
re-charset
(re-pattern (str ";(?:.*\\s)?(?i:charset)=(?:" re-value ")\\s*(?:;|$)")))
(defn find-content-type-charset
"Return the charset of a given a content-type string."
[s]
(when-let [m (re-find re-charset s)]
(or (m 1) (m 2))))
(defn character-encoding
"Return the character encoding for the request, or nil if it is not set."
[request]
(some-> (get-in request [:headers "content-type"])
find-content-type-charset))
(defn header
"Returns an updated Ring response with the specified header added."
[resp name value]
(assoc-in resp [:headers name] (str value)))
(defn content-type
"Returns an updated Ring response with the a 'content-type header corresponding
to the given content-type."
[resp content-type]
(header resp "content-type" content-type))
(defn- json-request? [request]
(when-let [type (get-in request [:headers "content-type"])]
(seq (re-find #"^application/(.+\+)?json" type))))
(defn- read-json [request & [{:keys [keywords? key-fn]}]]
(when (json-request? request)
(when-let [^InputStream body (:body request)]
(let [^String encoding (or (character-encoding request)
"UTF-8")
body-reader (java.io.InputStreamReader. body encoding)]
(try
[true (json/parse-stream body-reader (or key-fn keywords?))]
(catch Exception _
(println "Error parsing json stream")
[false nil]))))))
(def ^{:doc "The default response to return when a JSON request is malformed."}
default-malformed-response
{:status 400
:headers {"content-type" "text/plain"}
:body "Malformed JSON in request body."})
(defn json-body-request
"Parse a JSON request body and assoc it back into the :body key. Returns nil
if the JSON is malformed. See: wrap-json-body-request."
[request options]
(if-let [[valid? json] (read-json request options)]
(when valid? (assoc request :body json))
request))
(defn wrap-json-body-request
"Middleware that parses the body of JSON request maps, and replaces the :body
key with the parsed data structure. Requests without a JSON content type are
unaffected.
Accepts the following options:
:key-fn - function that will be applied to each key
:keywords? - true if the keys of maps should be turned into keywords
:bigdecimals? - true if BigDecimals should be used instead of Doubles
:malformed-response - a response map to return when the JSON is malformed"
{:arglists '([handler] [handler options])}
[handler & [{:keys [malformed-response]
:or {malformed-response default-malformed-response}
:as options}]]
(fn
([request]
(if-let [request (json-body-request request options)]
(handler request)
malformed-response))
([request respond raise]
(if-let [request (json-body-request request options)]
(handler request respond raise)
(respond malformed-response)))))
(defn- assoc-json-params [request json]
(if (map? json)
(-> request
(assoc :json-params json)
(update-in [:params] merge json))
request))
(defn json-params-request
"Parse the body of JSON requests into a map of parameters, which are added
to the request map on the :json-params and :params keys. Returns nil if the
JSON is malformed. See: wrap-json-params."
[request options]
(if-let [[valid? json] (read-json request options)]
(when valid? (assoc-json-params request json))
request))
(defn wrap-json-params
"Middleware that parses the body of JSON requests into a map of parameters,
which are added to the request map on the :json-params and :params keys.
Accepts the following options:
:key-fn - function that will be applied to each key
:bigdecimals? - true if BigDecimals should be used instead of Doubles
:malformed-response - a response map to return when the JSON is malformed
Use the standard Ring middleware, ring.middleware.keyword-params, to
convert the parameters into keywords."
{:arglists '([handler] [handler options])}
[handler & [{:keys [malformed-response]
:or {malformed-response default-malformed-response}
:as options}]]
(fn
([request]
(if-let [request (json-params-request request options)]
(handler request)
malformed-response))
([request respond raise]
(if-let [request (json-params-request request options)]
(handler request respond raise)
(respond malformed-response)))))
(defn json-response
"Converts responses with a map or a vector for a body into a JSON response.
See: wrap-json-response."
[response options]
(if (coll? (:body response))
(let [json-resp (update-in response [:body] json/generate-string options)]
(if (contains? (:headers response) "content-type")
json-resp
(content-type json-resp "application/json; charset=utf-8")))
response))
(defn wrap-json-response
"Middleware that converts responses with a map or a vector for a body into a
JSON response.
Accepts the following options:
:key-fn - function that will be applied to each key
:pretty - true if the JSON should be pretty-printed
:escape-non-ascii - true if non-ASCII characters should be escaped with \\u
:stream? - true to create JSON body as stream rather than string"
{:arglists '([handler] [handler options])}
[handler & [{:as options}]]
(fn
([request]
(json-response (handler request) options))
([request respond raise]
(handler request (fn [response] (respond (json-response response options))) raise))))
(def wrap-json-body (comp wrap-json-response wrap-json-body-request))