1use bytes::Bytes;
8use http::{HeaderMap, StatusCode};
9
10use http::header::CONTENT_TYPE;
11
12use crate::error::Error;
13use crate::Result;
14
15#[derive(Debug, Clone)]
17pub enum ResponseBodyKind {
18 Empty,
20 Text(String),
22 #[cfg(feature = "json")]
24 Json(serde_json::Value),
25 Bytes(Bytes),
27}
28
29#[derive(Clone)]
40pub struct Response {
41 status: StatusCode,
42 headers: HeaderMap,
43 body: Bytes,
44 url: Option<url::Url>,
45 #[cfg(feature = "json")]
46 json_parser: Option<crate::json_parser::JsonParserFn>,
47}
48
49impl Response {
50 pub(crate) fn new(
51 status: StatusCode,
52 headers: HeaderMap,
53 body: Bytes,
54 url: Option<url::Url>,
55 #[cfg(feature = "json")] json_parser: Option<crate::json_parser::JsonParserFn>,
56 ) -> Self {
57 Self {
58 status,
59 headers,
60 body,
61 url,
62 #[cfg(feature = "json")]
63 json_parser,
64 }
65 }
66
67 pub fn status(&self) -> StatusCode {
69 self.status
70 }
71
72 pub fn headers(&self) -> &HeaderMap {
74 &self.headers
75 }
76
77 pub fn bytes(&self) -> &Bytes {
79 &self.body
80 }
81
82 pub fn url(&self) -> Option<&url::Url> {
84 self.url.as_ref()
85 }
86
87 pub fn is_success(&self) -> bool {
89 self.status.is_success()
90 }
91
92 #[must_use = "call `?` or handle the error explicitly"]
94 pub fn error_for_status(&self) -> Result<()> {
95 if self.status.is_success() {
96 return Ok(());
97 }
98 Err(Error::http_with_status_text(
99 self.status,
100 self.status.canonical_reason().unwrap_or("request failed"),
101 self.status.canonical_reason().unwrap_or("request failed"),
102 Some(self.body.clone()),
103 ))
104 }
105
106 pub fn into_text(self) -> Result<String> {
110 self.error_for_status()?;
111 Ok(String::from_utf8_lossy(&self.body).into_owned())
112 }
113
114 pub async fn text(self) -> Result<String> {
116 self.into_text()
117 }
118
119 pub fn into_bytes_checked(self) -> Result<Bytes> {
123 self.error_for_status()?;
124 Ok(self.body)
125 }
126
127 pub async fn bytes_checked(self) -> Result<Bytes> {
129 self.into_bytes_checked()
130 }
131
132 #[cfg(feature = "json")]
138 pub fn into_json<T: serde::de::DeserializeOwned>(self) -> Result<T> {
139 self.error_for_status()?;
140 crate::json_parser::deserialize(&self.body, self.status, self.json_parser.as_ref())
141 }
142
143 #[cfg(feature = "json")]
145 pub async fn json<T: serde::de::DeserializeOwned>(self) -> Result<T> {
146 self.into_json()
147 }
148
149 #[cfg(feature = "json")]
155 pub fn into_json_with<T, F>(self, parse: F) -> Result<T>
156 where
157 T: serde::de::DeserializeOwned,
158 F: FnOnce(&Bytes) -> std::result::Result<T, String>,
159 {
160 self.error_for_status()?;
161 parse(&self.body).map_err(|message| {
162 crate::json_parser::deserialize_error(self.status, message, &self.body)
163 })
164 }
165
166 #[cfg(feature = "json")]
168 pub async fn json_with<T, F>(self, parse: F) -> Result<T>
169 where
170 T: serde::de::DeserializeOwned,
171 F: FnOnce(&Bytes) -> std::result::Result<T, String>,
172 {
173 self.into_json_with(parse)
174 }
175
176 #[cfg(feature = "json")]
178 pub fn into_json_unchecked<T: serde::de::DeserializeOwned>(self) -> Result<T> {
179 crate::json_parser::deserialize(&self.body, self.status, self.json_parser.as_ref())
180 }
181
182 #[cfg(feature = "json")]
184 pub async fn json_unchecked<T: serde::de::DeserializeOwned>(self) -> Result<T> {
185 self.into_json_unchecked()
186 }
187
188 #[cfg(feature = "validate")]
190 pub fn into_json_validated<T>(self) -> Result<T>
191 where
192 T: serde::de::DeserializeOwned + garde::Validate,
193 T::Context: Default,
194 {
195 self.error_for_status()?;
196 crate::validate_json::deserialize_validated(
197 &self.body,
198 self.status,
199 self.json_parser.as_ref(),
200 )
201 }
202
203 #[cfg(feature = "validate")]
205 pub async fn json_validated<T>(self) -> Result<T>
206 where
207 T: serde::de::DeserializeOwned + garde::Validate,
208 T::Context: Default,
209 {
210 self.into_json_validated()
211 }
212
213 #[cfg(feature = "validate")]
215 pub fn into_json_validated_unchecked<T>(self) -> Result<T>
216 where
217 T: serde::de::DeserializeOwned + garde::Validate,
218 T::Context: Default,
219 {
220 crate::validate_json::deserialize_validated(
221 &self.body,
222 self.status,
223 self.json_parser.as_ref(),
224 )
225 }
226
227 #[cfg(feature = "validate")]
229 pub async fn json_validated_unchecked<T>(self) -> Result<T>
230 where
231 T: serde::de::DeserializeOwned + garde::Validate,
232 T::Context: Default,
233 {
234 self.into_json_validated_unchecked()
235 }
236
237 pub fn into_parts(self) -> (StatusCode, HeaderMap, Bytes) {
239 (self.status, self.headers, self.body)
240 }
241
242 pub fn body_by_content_type(&self) -> ResponseBodyKind {
244 if self.body.is_empty() {
245 return ResponseBodyKind::Empty;
246 }
247
248 let mime = self
249 .headers
250 .get(CONTENT_TYPE)
251 .and_then(|v| v.to_str().ok())
252 .unwrap_or("")
253 .split(';')
254 .next()
255 .unwrap_or("")
256 .trim()
257 .to_ascii_lowercase();
258
259 #[cfg(feature = "json")]
260 if mime.contains("json") {
261 if let Ok(value) = serde_json::from_slice(&self.body) {
262 return ResponseBodyKind::Json(value);
263 }
264 }
265
266 if mime.starts_with("text/") || mime == "application/xml" || mime == "application/xhtml+xml"
267 {
268 return ResponseBodyKind::Text(String::from_utf8_lossy(&self.body).into_owned());
269 }
270
271 if mime.is_empty() {
272 if let Ok(text) = std::str::from_utf8(&self.body) {
273 if text
274 .chars()
275 .all(|c| !c.is_control() || c == '\n' || c == '\r' || c == '\t')
276 {
277 return ResponseBodyKind::Text(text.to_string());
278 }
279 }
280 }
281
282 ResponseBodyKind::Bytes(self.body.clone())
283 }
284
285 pub fn into_body_by_content_type(self) -> Result<ResponseBodyKind> {
287 self.error_for_status()?;
288 Ok(self.body_by_content_type())
289 }
290}
291
292impl std::fmt::Debug for Response {
293 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294 let mut debug = f.debug_struct("Response");
295 debug
296 .field("status", &self.status)
297 .field("headers", &self.headers)
298 .field("body", &self.body)
299 .field("url", &self.url);
300 #[cfg(feature = "json")]
301 if self.json_parser.is_some() {
302 debug.field("json_parser", &"<custom>");
303 }
304 debug.finish()
305 }
306}
307
308#[cfg(all(test, feature = "json"))]
309mod tests {
310 use super::*;
311 use serde::Deserialize;
312
313 #[derive(Debug, Deserialize, PartialEq)]
314 struct IdOnly {
315 id: u64,
316 }
317
318 #[test]
319 fn into_text_returns_body_on_success() {
320 let response = Response::new(
321 StatusCode::OK,
322 HeaderMap::new(),
323 Bytes::from_static(b"hello"),
324 None,
325 None,
326 );
327 assert_eq!(response.into_text().unwrap(), "hello");
328 }
329
330 #[test]
331 fn into_json_deserializes_without_async() {
332 let response = Response::new(
333 StatusCode::OK,
334 HeaderMap::new(),
335 Bytes::from_static(br#"{"id":7}"#),
336 None,
337 None,
338 );
339 assert_eq!(response.into_json::<IdOnly>().unwrap(), IdOnly { id: 7 });
340 }
341
342 #[test]
343 fn into_json_with_strips_bom_without_client_parser() {
344 let response = Response::new(
345 StatusCode::OK,
346 HeaderMap::new(),
347 Bytes::from_static(b"\xef\xbb\xbf{\"id\":3}"),
348 None,
349 None,
350 );
351 let parsed: IdOnly = response
352 .into_json_with(|body| {
353 let slice = body.strip_prefix(b"\xef\xbb\xbf").unwrap_or(body);
354 serde_json::from_slice(slice).map_err(|e| e.to_string())
355 })
356 .unwrap();
357 assert_eq!(parsed, IdOnly { id: 3 });
358 }
359}