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)]
45pub struct Response<Body> {
46 #[serde(skip, default)]
47 version: Option<Version>,
48 #[serde(with = "status_serde")]
49 status: StatusCode,
50 #[serde(with = "header_serde")]
51 headers: HeaderMap,
52 body: Option<Body>,
53}
54
55impl<Body> Response<Body> {
56 pub(crate) fn new(mut res: super::RawResponse) -> Result<Response<Vec<u8>>> {
63 let body = res.body_bytes()?;
64 let status = res.status();
65 let headers = res.as_ref().clone();
66
67 if status.is_client_error() || status.is_server_error() {
68 return Err(HttpError::Http {
69 code: status.as_u16(),
70 message: status.to_string(),
71 headers: Box::new(headers),
72 body,
73 });
74 }
75
76 Ok(Response {
77 status,
78 headers,
79 version: res.version(),
80 body: Some(body),
81 })
82 }
83
84 #[allow(clippy::missing_const_for_fn)]
98 pub fn status(&self) -> StatusCode {
99 self.status
100 }
101
102 #[allow(clippy::missing_const_for_fn)]
112 pub fn version(&self) -> Option<Version> {
113 self.version
114 }
115
116 pub fn header_all(
118 &self,
119 name: impl http::header::AsHeaderName,
120 ) -> http::header::GetAll<'_, HeaderValue> {
121 self.headers.get_all(name)
122 }
123
124 pub fn header(&self, name: impl http::header::AsHeaderName) -> Option<&HeaderValue> {
135 self.headers.get(name)
136 }
137
138 pub fn header_mut(
140 &mut self,
141 name: impl http::header::AsHeaderName,
142 ) -> Option<&mut HeaderValue> {
143 self.headers.get_mut(name)
144 }
145
146 pub fn remove_header(&mut self, name: impl http::header::AsHeaderName) -> Option<HeaderValue> {
148 self.headers.remove(name)
149 }
150
151 pub fn insert_header(
155 &mut self,
156 name: impl http::header::IntoHeaderName,
157 value: HeaderValue,
158 ) -> Option<HeaderValue> {
159 self.headers.insert(name, value)
160 }
161
162 pub fn append_header(
167 &mut self,
168 name: impl http::header::IntoHeaderName,
169 value: HeaderValue,
170 ) -> bool {
171 self.headers.append(name, value)
172 }
173
174 #[must_use]
176 pub fn iter(&self) -> http::header::Iter<'_, HeaderValue> {
177 self.headers.iter()
178 }
179
180 #[must_use]
182 pub fn iter_mut(&mut self) -> http::header::IterMut<'_, HeaderValue> {
183 self.headers.iter_mut()
184 }
185
186 #[must_use]
188 pub fn header_names(&self) -> http::header::Keys<'_, HeaderValue> {
189 self.headers.keys()
190 }
191
192 #[must_use]
194 pub fn header_values(&self) -> http::header::Values<'_, HeaderValue> {
195 self.headers.values()
196 }
197
198 pub fn content_type(&self) -> Option<mime::Mime> {
209 self.headers
210 .get(http::header::CONTENT_TYPE)?
211 .to_str()
212 .ok()?
213 .parse()
214 .ok()
215 }
216
217 #[allow(clippy::missing_const_for_fn)]
218 pub fn body(&self) -> Option<&Body> {
219 self.body.as_ref()
220 }
221
222 #[allow(clippy::missing_const_for_fn)]
223 pub fn take_body(&mut self) -> Option<Body> {
224 self.body.take()
225 }
226
227 pub fn with_body<NewBody>(self, body: NewBody) -> Response<NewBody> {
228 Response {
229 body: Some(body),
230 headers: self.headers,
231 status: self.status,
232 version: self.version,
233 }
234 }
235}
236
237impl<'a, Body> IntoIterator for &'a Response<Body> {
238 type Item = (&'a HeaderName, &'a HeaderValue);
239 type IntoIter = http::header::Iter<'a, HeaderValue>;
240 fn into_iter(self) -> Self::IntoIter {
241 self.iter()
242 }
243}
244
245impl<'a, Body> IntoIterator for &'a mut Response<Body> {
246 type Item = (&'a HeaderName, &'a mut HeaderValue);
247 type IntoIter = http::header::IterMut<'a, HeaderValue>;
248 fn into_iter(self) -> Self::IntoIter {
249 self.iter_mut()
250 }
251}
252
253impl Response<Vec<u8>> {
254 pub(crate) fn new_with_status(status: StatusCode) -> Self {
255 Self {
256 status,
257 headers: HeaderMap::new(),
258 version: None,
259 body: None,
260 }
261 }
262
263 pub fn body_bytes(&mut self) -> Result<Vec<u8>> {
282 self.body.take().ok_or(HttpError::BodyAlreadyTaken)
283 }
284
285 pub fn body_string(&mut self) -> Result<String> {
304 let bytes = self.body_bytes()?;
305 let mime = self.content_type();
306 let claimed_encoding = mime
307 .as_ref()
308 .and_then(|m| m.get_param(mime::CHARSET))
309 .map(|name| name.as_str().to_owned());
310 Ok(decode_body(bytes, claimed_encoding.as_deref())?)
311 }
312
313 pub fn body_json<T: DeserializeOwned>(&mut self) -> Result<T> {
335 let body_bytes = self.body_bytes()?;
336 serde_json::from_slice(&body_bytes).map_err(HttpError::from)
337 }
338}
339
340impl<Body> AsRef<HeaderMap> for Response<Body> {
341 fn as_ref(&self) -> &HeaderMap {
342 &self.headers
343 }
344}
345
346impl<Body> AsMut<HeaderMap> for Response<Body> {
347 fn as_mut(&mut self) -> &mut HeaderMap {
348 &mut self.headers
349 }
350}
351
352impl<Body> fmt::Debug for Response<Body> {
353 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354 f.debug_struct("Response")
355 .field("version", &self.version)
356 .field("status", &self.status)
357 .field("headers", &self.headers)
358 .finish_non_exhaustive()
359 }
360}
361
362impl<Body> Index<&str> for Response<Body> {
363 type Output = HeaderValue;
364
365 #[inline]
371 fn index(&self, name: &str) -> &HeaderValue {
372 &self.headers[name]
373 }
374}
375
376impl<Body> PartialEq for Response<Body>
377where
378 Body: PartialEq,
379{
380 fn eq(&self, other: &Self) -> bool {
381 self.status == other.status && self.headers == other.headers && self.body == other.body
382 }
383}
384
385impl<Body> Eq for Response<Body> where Body: Eq {}
386
387impl<Body> TryFrom<Response<Body>> for http::Response<Body> {
388 type Error = ();
389
390 fn try_from(res: Response<Body>) -> std::result::Result<Self, ()> {
391 let body = res.body.ok_or(())?;
392 let mut builder = http::Response::builder().status(res.status);
393 if let Some(v) = res.version {
394 builder = builder.version(v);
395 }
396 for (name, value) in &res.headers {
397 builder = builder.header(name, value);
398 }
399 builder.body(body).map_err(|_| ())
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use http::{HeaderMap, StatusCode};
406
407 use crate::{
408 HttpError, HttpResponse, RawResponse, response::Response, testing::ResponseBuilder,
409 };
410
411 #[test]
412 fn status_is_http_status_code() {
413 let res = ResponseBuilder::ok().build();
414 assert_eq!(res.status(), StatusCode::OK);
415 assert_eq!(res.status().as_u16(), 200);
416 }
417
418 #[test]
419 fn headers_are_http_header_map() {
420 let res = ResponseBuilder::ok().header("x-custom", "hello").build();
421 let map: &HeaderMap = res.as_ref();
422 assert_eq!(map["x-custom"], "hello");
423 }
424
425 #[test]
426 fn header_all_returns_multiple_values() {
427 let res = ResponseBuilder::ok()
428 .header("accept", "text/html")
429 .append_header("accept", "application/json")
430 .build();
431 let values: Vec<&str> = res
432 .header_all("accept")
433 .iter()
434 .map(|v| v.to_str().unwrap())
435 .collect();
436 assert_eq!(values, ["text/html", "application/json"]);
437 }
438
439 #[test]
440 fn native_try_from_into_http_response() {
441 use std::convert::TryFrom;
442 let res: Response<Vec<u8>> = ResponseBuilder::ok()
443 .header("x-foo", "bar")
444 .body(b"hello".to_vec())
445 .build();
446 let http_res = http::Response::<Vec<u8>>::try_from(res).unwrap();
447 assert_eq!(http_res.status(), StatusCode::OK);
448 assert_eq!(http_res.headers()["x-foo"], "bar");
449 assert_eq!(http_res.body(), b"hello");
450 }
451
452 #[futures_test::test]
454 async fn http_response_round_trip() {
455 use crate::protocol::HttpResponse;
456 use std::convert::TryFrom;
457
458 let http_response = HttpResponse::ok()
459 .header("content-type", "application/json")
460 .json(serde_json::json!({"data": 42}))
461 .build();
462
463 let response_async = RawResponse::try_from(http_response).expect("valid status");
465
466 let response = Response::<Vec<u8>>::new(response_async).expect("should decode");
468
469 assert_eq!(response.status().as_u16(), 200);
470 assert_eq!(response.content_type(), Some(mime::APPLICATION_JSON));
471
472 let http_resp = http::Response::<Vec<u8>>::try_from(response).unwrap();
474 assert_eq!(http_resp.status(), 200);
475 assert_eq!(http_resp.headers()["content-type"], "application/json");
476 let parsed: serde_json::Value = serde_json::from_slice(http_resp.body()).unwrap();
477 assert_eq!(parsed["data"], 42);
478 }
479
480 #[test]
481 fn response_status_serde_roundtrip() {
482 let res: Response<Vec<u8>> = ResponseBuilder::ok().body(vec![42u8]).build();
483 let json = serde_json::to_string(&res).expect("should serialize");
484 let back: Response<Vec<u8>> = serde_json::from_str(&json).expect("should deserialize");
485 assert_eq!(back.status().as_u16(), 200);
486 assert_eq!(back.body().unwrap(), &[42u8]);
487 }
488
489 #[test]
490 fn non_standard_status_499_becomes_http_error() {
491 let http_response = HttpResponse::status(499)
495 .body(b"client closed connection".to_vec())
496 .build();
497 let raw = RawResponse::try_from(http_response).expect("499 is a valid status code");
498 let result = Response::<Vec<u8>>::new(raw);
499
500 assert!(result.is_err());
501 let err = result.unwrap_err();
502 assert!(matches!(err, HttpError::Http { code, .. } if code == 499));
503 }
504
505 #[test]
506 fn non_standard_status_599_becomes_http_error() {
507 let http_response = HttpResponse::status(599)
508 .body(b"custom server error".to_vec())
509 .build();
510 let raw = RawResponse::try_from(http_response).expect("599 is a valid status code");
511 let result = Response::<Vec<u8>>::new(raw);
512
513 assert!(result.is_err());
514 let err = result.unwrap_err();
515 assert!(matches!(err, HttpError::Http { code, .. } if code == 599));
516 }
517
518 #[test]
519 fn non_standard_4xx_status_preserves_code_in_error() {
520 for status in [490, 491, 492, 493, 494, 495, 496, 497, 498, 499] {
521 let http_response = HttpResponse::status(status).body(b"".to_vec()).build();
522 let raw = RawResponse::try_from(http_response)
523 .unwrap_or_else(|_| panic!("{status} is a valid status code"));
524 let result = Response::<Vec<u8>>::new(raw);
525
526 let err = result.expect_err("should be an error");
527 assert!(
528 matches!(err, HttpError::Http { code, .. } if code == status),
529 "Expected status {status} to be preserved in HttpError, got: {err:?}"
530 );
531 }
532 }
533
534 #[test]
535 fn response_serde_roundtrip_with_non_standard_status() {
536 let res: Response<Vec<u8>> = ResponseBuilder::with_status(299)
539 .body(b"test".to_vec())
540 .build();
541 let json = serde_json::to_string(&res).expect("should serialize");
542 let back: Response<Vec<u8>> = serde_json::from_str(&json).expect("should deserialize");
543 assert_eq!(back.status().as_u16(), 299);
544 }
545
546 #[test]
547 fn body_bytes_returns_error_when_body_already_taken() {
548 let mut res: Response<Vec<u8>> = ResponseBuilder::ok().body(b"hello".to_vec()).build();
549 let _ = res.body_bytes().unwrap();
550 let err = res.body_bytes().expect_err("second call must fail");
551
552 assert!(matches!(err, HttpError::BodyAlreadyTaken), "got: {err:?}");
555 assert_eq!(err.code(), None);
556 }
557
558 #[test]
559 fn try_from_response_with_no_body_returns_err() {
560 let res = Response::<Vec<u8>>::new_with_status(StatusCode::OK);
562 let result = http::Response::<Vec<u8>>::try_from(res);
563 assert!(result.is_err(), "TryFrom must return Err when body is None");
564 }
565
566 #[test]
567 fn multi_value_headers_survive_serde_roundtrip() {
568 let res: Response<Vec<u8>> = ResponseBuilder::ok()
569 .header("set-cookie", "a=1")
570 .append_header("set-cookie", "b=2")
571 .body(b"".to_vec())
572 .build();
573
574 let json = serde_json::to_string(&res).expect("should serialize");
575 let back: Response<Vec<u8>> = serde_json::from_str(&json).expect("should deserialize");
576
577 let values: Vec<&str> = back
578 .header_all("set-cookie")
579 .iter()
580 .map(|v| v.to_str().unwrap())
581 .collect();
582 assert_eq!(
583 values.len(),
584 2,
585 "both Set-Cookie values must survive serde: {values:?}"
586 );
587 assert!(values.contains(&"a=1"));
588 assert!(values.contains(&"b=2"));
589 }
590}
591
592mod status_serde {
594 use http::StatusCode;
595 use serde::{Deserialize, Deserializer, Serializer};
596
597 #[allow(clippy::trivially_copy_pass_by_ref)]
598 pub fn serialize<S: Serializer>(status: &StatusCode, ser: S) -> Result<S::Ok, S::Error> {
599 ser.serialize_u16(status.as_u16())
600 }
601
602 pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<StatusCode, D::Error> {
603 let n = u16::deserialize(de)?;
604 StatusCode::from_u16(n).map_err(serde::de::Error::custom)
605 }
606}
607
608mod header_serde {
609 use http::{HeaderMap, HeaderName, HeaderValue};
610 use serde::{Deserializer, Serializer, de::Error};
611 use std::str::FromStr;
612
613 pub fn serialize<S>(headers: &HeaderMap, serializer: S) -> Result<S::Ok, S::Error>
614 where
615 S: Serializer,
616 {
617 let mut map: std::collections::BTreeMap<&str, Vec<&str>> =
621 std::collections::BTreeMap::new();
622 for (name, value) in headers {
623 map.entry(name.as_str())
624 .or_default()
625 .push(value.to_str().unwrap_or(""));
626 }
627 serializer.collect_map(map.iter())
628 }
629
630 pub fn deserialize<'de, D>(deserializer: D) -> Result<HeaderMap, D::Error>
631 where
632 D: Deserializer<'de>,
633 {
634 let strs =
636 <std::collections::HashMap<String, Vec<String>> as serde::Deserialize>::deserialize(
637 deserializer,
638 )?;
639 let mut headers = HeaderMap::new();
640 for (name, values) in strs {
641 let name = HeaderName::from_str(&name).map_err(D::Error::custom)?;
642 for value in values {
643 let value = HeaderValue::from_str(&value).map_err(D::Error::custom)?;
644 headers.append(name.clone(), value);
645 }
646 }
647 Ok(headers)
648 }
649}