apimock_server/types.rs
1use http_body_util::BodyExt;
2use hyper::body::Bytes;
3
4use std::convert::Infallible;
5
6pub type BoxBody = http_body_util::combinators::BoxBody<Bytes, Infallible>;
7
8/// A response fully read into memory — status, headers, body — rather
9/// than the streaming `hyper::Response<BoxBody>` every dispatch function
10/// returns.
11///
12/// # Why this exists (RFC 055)
13///
14/// `apimock get` answers a question; it does not write to a socket.
15/// Reusing `rule_set_response`/`dyn_route_content`/`respond_response`
16/// unchanged (the RFC's own "one implementation of matching" principle)
17/// still leaves a `BoxBody` on the way out, and a CLI command has
18/// nothing to stream it *to* — it needs the bytes, once, to print or
19/// serialise. This is the minimal additive surface that gap needed:
20/// no new dispatch logic, just a collector for what dispatch already
21/// produces.
22pub struct CollectedResponse {
23 pub status: hyper::StatusCode,
24 /// Header order preserved as received; values that aren't valid
25 /// UTF-8 are dropped, matching how `RequestSummary`'s own header
26 /// collection (`trace.rs`) already treats non-UTF-8 values.
27 pub headers: Vec<(String, String)>,
28 pub body: Vec<u8>,
29}
30
31impl CollectedResponse {
32 /// Consume a streaming response and read it fully into memory.
33 pub async fn collect(response: hyper::Response<BoxBody>) -> Self {
34 let (parts, body) = response.into_parts();
35 // `BoxBody`'s error type is `Infallible` (see the alias above) —
36 // collection cannot fail.
37 let bytes = body
38 .collect()
39 .await
40 .expect("BoxBody's error type is Infallible")
41 .to_bytes();
42 let headers = parts
43 .headers
44 .iter()
45 .filter_map(|(k, v)| v.to_str().ok().map(|v| (k.to_string(), v.to_owned())))
46 .collect();
47 Self {
48 status: parts.status,
49 headers,
50 body: bytes.to_vec(),
51 }
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use http_body_util::Full;
59
60 #[tokio::test]
61 async fn collect_reads_status_headers_and_body() {
62 let response = hyper::Response::builder()
63 .status(201)
64 .header("x-custom", "yes")
65 .body(
66 Full::new(Bytes::from_static(b"hello"))
67 .map_err(|e| match e {})
68 .boxed(),
69 )
70 .unwrap();
71
72 let collected = CollectedResponse::collect(response).await;
73 assert_eq!(collected.status, hyper::StatusCode::CREATED);
74 assert_eq!(collected.body, b"hello");
75 assert!(
76 collected
77 .headers
78 .iter()
79 .any(|(k, v)| k == "x-custom" && v == "yes")
80 );
81 }
82}