1use super::decode::decode_body;
2use crate::{HttpError, Result};
3use http::{HeaderMap, HeaderName, HeaderValue, StatusCode, Version};
4use serde::de::DeserializeOwned;
5use std::{fmt, ops::Index};
6
7#[derive(Clone, serde::Serialize, serde::Deserialize)]
9pub struct Response<Body> {
10 #[serde(skip, default)]
11 version: Option<Version>,
12 #[serde(with = "status_serde")]
13 status: StatusCode,
14 #[serde(with = "header_serde")]
15 headers: HeaderMap,
16 body: Option<Body>,
17}
18
19impl<Body> Response<Body> {
20 pub(crate) fn new(mut res: super::RawResponse) -> Result<Response<Vec<u8>>> {
22 let body = res.body_bytes()?;
23 let status = res.status();
24
25 if status.is_client_error() || status.is_server_error() {
26 return Err(HttpError::Http {
27 code: status.as_u16(),
28 message: status.to_string(),
29 body: Some(body),
30 });
31 }
32
33 let headers = res.as_ref().clone();
34
35 Ok(Response {
36 status,
37 headers,
38 version: res.version(),
39 body: Some(body),
40 })
41 }
42
43 #[allow(clippy::missing_const_for_fn)]
52 pub fn status(&self) -> StatusCode {
53 self.status
54 }
55
56 #[allow(clippy::missing_const_for_fn)]
66 pub fn version(&self) -> Option<Version> {
67 self.version
68 }
69
70 pub fn header_all(
72 &self,
73 name: impl http::header::AsHeaderName,
74 ) -> http::header::GetAll<'_, HeaderValue> {
75 self.headers.get_all(name)
76 }
77
78 pub fn header(&self, name: impl http::header::AsHeaderName) -> Option<&HeaderValue> {
89 self.headers.get(name)
90 }
91
92 pub fn header_mut(
94 &mut self,
95 name: impl http::header::AsHeaderName,
96 ) -> Option<&mut HeaderValue> {
97 self.headers.get_mut(name)
98 }
99
100 pub fn remove_header(&mut self, name: impl http::header::AsHeaderName) -> Option<HeaderValue> {
102 self.headers.remove(name)
103 }
104
105 pub fn insert_header(
109 &mut self,
110 name: impl http::header::IntoHeaderName,
111 value: HeaderValue,
112 ) -> Option<HeaderValue> {
113 self.headers.insert(name, value)
114 }
115
116 pub fn append_header(
121 &mut self,
122 name: impl http::header::IntoHeaderName,
123 value: HeaderValue,
124 ) -> bool {
125 self.headers.append(name, value)
126 }
127
128 #[must_use]
130 pub fn iter(&self) -> http::header::Iter<'_, HeaderValue> {
131 self.headers.iter()
132 }
133
134 #[must_use]
136 pub fn iter_mut(&mut self) -> http::header::IterMut<'_, HeaderValue> {
137 self.headers.iter_mut()
138 }
139
140 #[must_use]
142 pub fn header_names(&self) -> http::header::Keys<'_, HeaderValue> {
143 self.headers.keys()
144 }
145
146 #[must_use]
148 pub fn header_values(&self) -> http::header::Values<'_, HeaderValue> {
149 self.headers.values()
150 }
151
152 pub fn content_type(&self) -> Option<mime::Mime> {
163 self.headers
164 .get(http::header::CONTENT_TYPE)?
165 .to_str()
166 .ok()?
167 .parse()
168 .ok()
169 }
170
171 #[allow(clippy::missing_const_for_fn)]
172 pub fn body(&self) -> Option<&Body> {
173 self.body.as_ref()
174 }
175
176 #[allow(clippy::missing_const_for_fn)]
177 pub fn take_body(&mut self) -> Option<Body> {
178 self.body.take()
179 }
180
181 pub fn with_body<NewBody>(self, body: NewBody) -> Response<NewBody> {
182 Response {
183 body: Some(body),
184 headers: self.headers,
185 status: self.status,
186 version: self.version,
187 }
188 }
189}
190
191impl<'a, Body> IntoIterator for &'a Response<Body> {
192 type Item = (&'a HeaderName, &'a HeaderValue);
193 type IntoIter = http::header::Iter<'a, HeaderValue>;
194 fn into_iter(self) -> Self::IntoIter {
195 self.iter()
196 }
197}
198
199impl<'a, Body> IntoIterator for &'a mut Response<Body> {
200 type Item = (&'a HeaderName, &'a mut HeaderValue);
201 type IntoIter = http::header::IterMut<'a, HeaderValue>;
202 fn into_iter(self) -> Self::IntoIter {
203 self.iter_mut()
204 }
205}
206
207impl Response<Vec<u8>> {
208 pub(crate) fn new_with_status(status: StatusCode) -> Self {
209 Self {
210 status,
211 headers: HeaderMap::new(),
212 version: None,
213 body: None,
214 }
215 }
216
217 pub fn body_bytes(&mut self) -> Result<Vec<u8>> {
235 self.body.take().ok_or_else(|| HttpError::Http {
236 code: self.status().as_u16(),
237 message: "Body had no bytes".to_string(),
238 body: None,
239 })
240 }
241
242 pub fn body_string(&mut self) -> Result<String> {
261 let bytes = self.body_bytes()?;
262 let mime = self.content_type();
263 let claimed_encoding = mime
264 .as_ref()
265 .and_then(|m| m.get_param(mime::CHARSET))
266 .map(|name| name.as_str().to_owned());
267 Ok(decode_body(bytes, claimed_encoding.as_deref())?)
268 }
269
270 pub fn body_json<T: DeserializeOwned>(&mut self) -> Result<T> {
292 let body_bytes = self.body_bytes()?;
293 serde_json::from_slice(&body_bytes).map_err(HttpError::from)
294 }
295}
296
297impl<Body> AsRef<HeaderMap> for Response<Body> {
298 fn as_ref(&self) -> &HeaderMap {
299 &self.headers
300 }
301}
302
303impl<Body> AsMut<HeaderMap> for Response<Body> {
304 fn as_mut(&mut self) -> &mut HeaderMap {
305 &mut self.headers
306 }
307}
308
309impl<Body> fmt::Debug for Response<Body> {
310 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311 f.debug_struct("Response")
312 .field("version", &self.version)
313 .field("status", &self.status)
314 .field("headers", &self.headers)
315 .finish_non_exhaustive()
316 }
317}
318
319impl<Body> Index<&str> for Response<Body> {
320 type Output = HeaderValue;
321
322 #[inline]
328 fn index(&self, name: &str) -> &HeaderValue {
329 &self.headers[name]
330 }
331}
332
333impl<Body> PartialEq for Response<Body>
334where
335 Body: PartialEq,
336{
337 fn eq(&self, other: &Self) -> bool {
338 self.status == other.status && self.headers == other.headers && self.body == other.body
339 }
340}
341
342impl<Body> Eq for Response<Body> where Body: Eq {}
343
344impl<Body> TryFrom<Response<Body>> for http::Response<Body> {
345 type Error = ();
346
347 fn try_from(res: Response<Body>) -> std::result::Result<Self, ()> {
348 let body = res.body.ok_or(())?;
349 let mut builder = http::Response::builder().status(res.status);
350 if let Some(v) = res.version {
351 builder = builder.version(v);
352 }
353 for (name, value) in &res.headers {
354 builder = builder.header(name, value);
355 }
356 builder.body(body).map_err(|_| ())
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use http::{HeaderMap, StatusCode};
363
364 use crate::{
365 HttpError, HttpResponse, RawResponse, response::Response, testing::ResponseBuilder,
366 };
367
368 #[test]
369 fn status_is_http_status_code() {
370 let res = ResponseBuilder::ok().build();
371 assert_eq!(res.status(), StatusCode::OK);
372 assert_eq!(res.status().as_u16(), 200);
373 }
374
375 #[test]
376 fn headers_are_http_header_map() {
377 let res = ResponseBuilder::ok().header("x-custom", "hello").build();
378 let map: &HeaderMap = res.as_ref();
379 assert_eq!(map["x-custom"], "hello");
380 }
381
382 #[test]
383 fn header_all_returns_multiple_values() {
384 let res = ResponseBuilder::ok()
385 .header("accept", "text/html")
386 .append_header("accept", "application/json")
387 .build();
388 let values: Vec<&str> = res
389 .header_all("accept")
390 .iter()
391 .map(|v| v.to_str().unwrap())
392 .collect();
393 assert_eq!(values, ["text/html", "application/json"]);
394 }
395
396 #[test]
397 fn native_try_from_into_http_response() {
398 use std::convert::TryFrom;
399 let res: Response<Vec<u8>> = ResponseBuilder::ok()
400 .header("x-foo", "bar")
401 .body(b"hello".to_vec())
402 .build();
403 let http_res = http::Response::<Vec<u8>>::try_from(res).unwrap();
404 assert_eq!(http_res.status(), StatusCode::OK);
405 assert_eq!(http_res.headers()["x-foo"], "bar");
406 assert_eq!(http_res.body(), b"hello");
407 }
408
409 #[futures_test::test]
411 async fn http_response_round_trip() {
412 use crate::protocol::HttpResponse;
413 use std::convert::TryFrom;
414
415 let http_response = HttpResponse::ok()
416 .header("content-type", "application/json")
417 .json(serde_json::json!({"data": 42}))
418 .build();
419
420 let response_async = RawResponse::try_from(http_response).expect("valid status");
422
423 let response = Response::<Vec<u8>>::new(response_async).expect("should decode");
425
426 assert_eq!(response.status().as_u16(), 200);
427 assert_eq!(response.content_type(), Some(mime::APPLICATION_JSON));
428
429 let http_resp = http::Response::<Vec<u8>>::try_from(response).unwrap();
431 assert_eq!(http_resp.status(), 200);
432 assert_eq!(http_resp.headers()["content-type"], "application/json");
433 let parsed: serde_json::Value = serde_json::from_slice(http_resp.body()).unwrap();
434 assert_eq!(parsed["data"], 42);
435 }
436
437 #[test]
438 fn response_status_serde_roundtrip() {
439 let res: Response<Vec<u8>> = ResponseBuilder::ok().body(vec![42u8]).build();
440 let json = serde_json::to_string(&res).expect("should serialize");
441 let back: Response<Vec<u8>> = serde_json::from_str(&json).expect("should deserialize");
442 assert_eq!(back.status().as_u16(), 200);
443 assert_eq!(back.body().unwrap(), &[42u8]);
444 }
445
446 #[test]
447 fn non_standard_status_499_becomes_http_error() {
448 let http_response = HttpResponse::status(499)
452 .body(b"client closed connection".to_vec())
453 .build();
454 let raw = RawResponse::try_from(http_response).expect("499 is a valid status code");
455 let result = Response::<Vec<u8>>::new(raw);
456
457 assert!(result.is_err());
458 let err = result.unwrap_err();
459 assert!(matches!(err, HttpError::Http { code, .. } if code == 499));
460 }
461
462 #[test]
463 fn non_standard_status_599_becomes_http_error() {
464 let http_response = HttpResponse::status(599)
465 .body(b"custom server error".to_vec())
466 .build();
467 let raw = RawResponse::try_from(http_response).expect("599 is a valid status code");
468 let result = Response::<Vec<u8>>::new(raw);
469
470 assert!(result.is_err());
471 let err = result.unwrap_err();
472 assert!(matches!(err, HttpError::Http { code, .. } if code == 599));
473 }
474
475 #[test]
476 fn non_standard_4xx_status_preserves_code_in_error() {
477 for status in [490, 491, 492, 493, 494, 495, 496, 497, 498, 499] {
478 let http_response = HttpResponse::status(status).body(b"".to_vec()).build();
479 let raw = RawResponse::try_from(http_response)
480 .unwrap_or_else(|_| panic!("{status} is a valid status code"));
481 let result = Response::<Vec<u8>>::new(raw);
482
483 let err = result.expect_err("should be an error");
484 assert!(
485 matches!(err, HttpError::Http { code, .. } if code == status),
486 "Expected status {status} to be preserved in HttpError, got: {err:?}"
487 );
488 }
489 }
490
491 #[test]
492 fn response_builder_with_non_standard_status_499() {
493 let res: Response<Vec<u8>> = ResponseBuilder::with_status(499).build();
494 assert_eq!(res.status().as_u16(), 499);
495 }
496
497 #[test]
498 fn response_serde_roundtrip_with_non_standard_status() {
499 let res: Response<Vec<u8>> = ResponseBuilder::with_status(499)
500 .body(b"test".to_vec())
501 .build();
502 let json = serde_json::to_string(&res).expect("should serialize");
503 let back: Response<Vec<u8>> = serde_json::from_str(&json).expect("should deserialize");
504 assert_eq!(back.status().as_u16(), 499);
505 }
506
507 #[test]
508 fn body_bytes_returns_error_when_body_already_taken() {
509 let mut res: Response<Vec<u8>> = ResponseBuilder::ok().body(b"hello".to_vec()).build();
510 let _ = res.body_bytes().unwrap();
511 let err = res.body_bytes().expect_err("second call must fail");
512 assert!(matches!(err, HttpError::Http { .. }));
513 }
514
515 #[test]
516 fn try_from_response_with_no_body_returns_err() {
517 let res = Response::<Vec<u8>>::new_with_status(StatusCode::OK);
519 let result = http::Response::<Vec<u8>>::try_from(res);
520 assert!(result.is_err(), "TryFrom must return Err when body is None");
521 }
522
523 #[test]
524 fn multi_value_headers_survive_serde_roundtrip() {
525 let res: Response<Vec<u8>> = ResponseBuilder::ok()
526 .header("set-cookie", "a=1")
527 .append_header("set-cookie", "b=2")
528 .body(b"".to_vec())
529 .build();
530
531 let json = serde_json::to_string(&res).expect("should serialize");
532 let back: Response<Vec<u8>> = serde_json::from_str(&json).expect("should deserialize");
533
534 let values: Vec<&str> = back
535 .header_all("set-cookie")
536 .iter()
537 .map(|v| v.to_str().unwrap())
538 .collect();
539 assert_eq!(
540 values.len(),
541 2,
542 "both Set-Cookie values must survive serde: {values:?}"
543 );
544 assert!(values.contains(&"a=1"));
545 assert!(values.contains(&"b=2"));
546 }
547}
548
549mod status_serde {
551 use http::StatusCode;
552 use serde::{Deserialize, Deserializer, Serializer};
553
554 #[allow(clippy::trivially_copy_pass_by_ref)]
555 pub fn serialize<S: Serializer>(status: &StatusCode, ser: S) -> Result<S::Ok, S::Error> {
556 ser.serialize_u16(status.as_u16())
557 }
558
559 pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<StatusCode, D::Error> {
560 let n = u16::deserialize(de)?;
561 StatusCode::from_u16(n).map_err(serde::de::Error::custom)
562 }
563}
564
565mod header_serde {
566 use http::{HeaderMap, HeaderName, HeaderValue};
567 use serde::{Deserializer, Serializer, de::Error};
568 use std::str::FromStr;
569
570 pub fn serialize<S>(headers: &HeaderMap, serializer: S) -> Result<S::Ok, S::Error>
571 where
572 S: Serializer,
573 {
574 let mut map: std::collections::BTreeMap<&str, Vec<&str>> =
578 std::collections::BTreeMap::new();
579 for (name, value) in headers {
580 map.entry(name.as_str())
581 .or_default()
582 .push(value.to_str().unwrap_or(""));
583 }
584 serializer.collect_map(map.iter())
585 }
586
587 pub fn deserialize<'de, D>(deserializer: D) -> Result<HeaderMap, D::Error>
588 where
589 D: Deserializer<'de>,
590 {
591 let strs =
593 <std::collections::HashMap<String, Vec<String>> as serde::Deserialize>::deserialize(
594 deserializer,
595 )?;
596 let mut headers = HeaderMap::new();
597 for (name, values) in strs {
598 let name = HeaderName::from_str(&name).map_err(D::Error::custom)?;
599 for value in values {
600 let value = HeaderValue::from_str(&value).map_err(D::Error::custom)?;
601 headers.append(name.clone(), value);
602 }
603 }
604 Ok(headers)
605 }
606}