Skip to main content

wiremock/
request.rs

1use std::fmt;
2
3use http::{HeaderMap, Method};
4use http_body_util::BodyExt;
5use serde::de::DeserializeOwned;
6use url::Url;
7
8pub const BODY_PRINT_LIMIT: usize = 10_000;
9
10/// Specifies limitations on printing request bodies when logging requests. For some mock servers
11/// the bodies may be too large to reasonably print and it may be desirable to limit them.
12#[derive(Debug, Copy, Clone)]
13pub enum BodyPrintLimit {
14    /// Maximum length of a body to print in bytes.
15    Limited(usize),
16    /// There is no limit to the size of a body that may be printed.
17    Unlimited,
18}
19
20/// An incoming request to an instance of [`MockServer`].
21///
22/// Each matcher gets an immutable reference to a `Request` instance in the [`matches`] method
23/// defined in the [`Match`] trait.
24///
25/// [`MockServer`]: crate::MockServer
26/// [`matches`]: crate::Match::matches
27/// [`Match`]: crate::Match
28///
29/// ### Implementation notes:
30/// We can't use `http_types::Request` directly in our `Match::matches` signature:
31/// it requires having mutable access to the request to extract the body (which gets
32/// consumed when read!).
33/// It would also require `matches` to be async, which is cumbersome due to the lack of async traits.
34///
35/// We introduce our `Request` type to perform this extraction once when the request
36/// arrives in the mock serve, store the result and pass an immutable reference to it
37/// to all our matchers.
38#[derive(Debug, Clone)]
39pub struct Request {
40    pub url: Url,
41    pub method: Method,
42    pub headers: HeaderMap,
43    pub body: Vec<u8>,
44}
45
46impl Request {
47    pub fn body_json<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
48        serde_json::from_slice(&self.body)
49    }
50
51    pub(crate) async fn from_hyper(request: hyper::Request<hyper::body::Incoming>) -> Request {
52        let (parts, body) = request.into_parts();
53        let url = match parts.uri.authority() {
54            Some(_) => parts.uri.to_string(),
55            None => format!("http://localhost{}", parts.uri),
56        }
57        .parse()
58        .unwrap();
59
60        let body = body
61            .collect()
62            .await
63            .expect("Failed to read request body.")
64            .to_bytes();
65
66        Self {
67            url,
68            method: parts.method,
69            headers: parts.headers,
70            body: body.to_vec(),
71        }
72    }
73
74    pub(crate) fn print_with_limit(
75        &self,
76        mut buffer: impl fmt::Write,
77        body_print_limit: BodyPrintLimit,
78    ) -> fmt::Result {
79        writeln!(buffer, "{} {}", self.method, self.url)?;
80        for name in self.headers.keys() {
81            let values = self
82                .headers
83                .get_all(name)
84                .iter()
85                .map(|value| String::from_utf8_lossy(value.as_bytes()))
86                .collect::<Vec<_>>();
87            let values = values.join(",");
88            writeln!(buffer, "{}: {}", name, values)?;
89        }
90
91        match body_print_limit {
92            BodyPrintLimit::Limited(limit) if self.body.len() > limit => {
93                let mut written = false;
94                for end_byte in limit..(limit + 4).max(self.body.len()) {
95                    if let Ok(truncated) = std::str::from_utf8(&self.body[..end_byte]) {
96                        written = true;
97                        writeln!(buffer, "{}", truncated)?;
98                        if end_byte < self.body.len() {
99                            writeln!(
100                                buffer,
101                                "We truncated the body because it was too large: {} bytes (limit: {} bytes)",
102                                self.body.len(),
103                                limit
104                            )?;
105                            writeln!(
106                                buffer,
107                                "Increase this limit by setting `WIREMOCK_BODY_PRINT_LIMIT`, or calling `MockServerBuilder::body_print_limit` when building your MockServer instance"
108                            )?;
109                        }
110                        break;
111                    }
112                }
113                if !written {
114                    writeln!(
115                        buffer,
116                        "Body is likely binary (invalid utf-8) size is {} bytes",
117                        self.body.len()
118                    )
119                } else {
120                    Ok(())
121                }
122            }
123            _ => {
124                if let Ok(body) = std::str::from_utf8(&self.body) {
125                    writeln!(buffer, "{}", body)
126                } else {
127                    writeln!(
128                        buffer,
129                        "Body is likely binary (invalid utf-8) size is {} bytes",
130                        self.body.len()
131                    )
132                }
133            }
134        }
135    }
136}