wiremock/
response_template.rs

1use http::{HeaderMap, HeaderName, HeaderValue, Response, StatusCode};
2use http_body_util::Full;
3use hyper::body::Bytes;
4use serde::Serialize;
5use std::convert::TryInto;
6use std::time::Duration;
7
8/// The blueprint for the response returned by a [`MockServer`] when a [`Mock`] matches on an incoming request.
9///
10/// [`Mock`]: crate::Mock
11/// [`MockServer`]: crate::MockServer
12#[derive(Clone, Debug)]
13pub struct ResponseTemplate {
14    mime: String,
15    status_code: StatusCode,
16    headers: HeaderMap,
17    body: Option<Vec<u8>>,
18    delay: Option<Duration>,
19}
20
21// `wiremock` is a crate meant for testing - failures are most likely not handled/temporary mistakes.
22// Hence we prefer to panic and provide an easier API than to use `Result`s thus pushing
23// the burden of "correctness" (and conversions) on the user.
24//
25// All methods try to accept the widest possible set of inputs and then perform the fallible conversion
26// internally, bailing if the fallible conversion fails.
27//
28// Same principle applies to allocation/cloning, freely used where convenient.
29impl ResponseTemplate {
30    /// Start building a `ResponseTemplate` specifying the status code of the response.
31    pub fn new<S>(s: S) -> Self
32    where
33        S: TryInto<StatusCode>,
34        <S as TryInto<StatusCode>>::Error: std::fmt::Debug,
35    {
36        let status_code = s.try_into().expect("Failed to convert into status code.");
37        Self {
38            status_code,
39            headers: HeaderMap::new(),
40            mime: String::new(),
41            body: None,
42            delay: None,
43        }
44    }
45
46    /// Append a header `value` to list of headers with `key` as header name.
47    ///
48    /// Unlike `insert_header`, this function will not override the contents of a header:
49    /// - if there are no header values with `key` as header name, it will insert one;
50    /// - if there are already some values with `key` as header name, it will append to the
51    ///   existing list.
52    pub fn append_header<K, V>(mut self, key: K, value: V) -> Self
53    where
54        K: TryInto<HeaderName>,
55        <K as TryInto<HeaderName>>::Error: std::fmt::Debug,
56        V: TryInto<HeaderValue>,
57        <V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
58    {
59        let key = key.try_into().expect("Failed to convert into header name.");
60        let value = value
61            .try_into()
62            .expect("Failed to convert into header value.");
63        self.headers.append(key, value);
64        self
65    }
66
67    /// Insert a header `value` with `key` as header name.
68    ///
69    /// This function will override the contents of a header:
70    /// - if there are no header values with `key` as header name, it will insert one;
71    /// - if there are already some values with `key` as header name, it will drop them and
72    ///   start a new list of header values, containing only `value`.
73    ///
74    /// ### Example:
75    /// ```rust
76    /// use wiremock::{MockServer, Mock, ResponseTemplate};
77    /// use wiremock::matchers::method;
78    ///
79    /// #[async_std::main]
80    /// async fn main() {
81    ///     // Arrange
82    ///     let mock_server = MockServer::start().await;
83    ///     let correlation_id = "1311db4f-fe65-4cb2-b514-1bb47f781aa7";
84    ///     let template = ResponseTemplate::new(200).insert_header(
85    ///         "X-Correlation-ID",
86    ///         correlation_id
87    ///     );
88    ///     Mock::given(method("GET"))
89    ///         .respond_with(template)
90    ///         .mount(&mock_server)
91    ///         .await;
92    ///
93    ///     // Act
94    ///     let res = surf::get(&mock_server.uri())
95    ///         .await
96    ///         .unwrap();
97    ///
98    ///     // Assert
99    ///     assert_eq!(res.header("X-Correlation-ID").unwrap().as_str(), correlation_id);
100    /// }
101    /// ```
102    pub fn insert_header<K, V>(mut self, key: K, value: V) -> Self
103    where
104        K: TryInto<HeaderName>,
105        <K as TryInto<HeaderName>>::Error: std::fmt::Debug,
106        V: TryInto<HeaderValue>,
107        <V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
108    {
109        let key = key.try_into().expect("Failed to convert into header name.");
110        let value = value
111            .try_into()
112            .expect("Failed to convert into header value.");
113        self.headers.insert(key, value);
114        self
115    }
116
117    /// Append multiple header key-value pairs.
118    ///
119    /// Existing header values will not be overridden.
120    ///
121    /// # Example
122    /// ```rust
123    /// use wiremock::{MockServer, Mock, ResponseTemplate};
124    /// use wiremock::matchers::method;
125    ///
126    /// #[async_std::main]
127    /// async fn main() {
128    ///     // Arrange
129    ///     let mock_server = MockServer::start().await;
130    ///     let headers = vec![
131    ///         ("Set-Cookie", "name=value"),
132    ///         ("Set-Cookie", "name2=value2; Domain=example.com"),
133    ///     ];
134    ///     let template = ResponseTemplate::new(200).append_headers(headers);
135    ///     Mock::given(method("GET"))
136    ///         .respond_with(template)
137    ///         .mount(&mock_server)
138    ///         .await;
139    ///
140    ///     // Act
141    ///     let res = surf::get(&mock_server.uri())
142    ///         .await
143    ///         .unwrap();
144    ///
145    ///     // Assert
146    ///     assert_eq!(res.header("Set-Cookie").unwrap().iter().count(), 2);
147    /// }
148    /// ```
149    pub fn append_headers<K, V, I>(mut self, headers: I) -> Self
150    where
151        K: TryInto<HeaderName>,
152        <K as TryInto<HeaderName>>::Error: std::fmt::Debug,
153        V: TryInto<HeaderValue>,
154        <V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
155        I: IntoIterator<Item = (K, V)>,
156    {
157        let headers = headers.into_iter().map(|(key, value)| {
158            (
159                key.try_into().expect("Failed to convert into header name."),
160                value
161                    .try_into()
162                    .expect("Failed to convert into header value."),
163            )
164        });
165        // The `Extend<(HeaderName, T)>` impl uses `HeaderMap::append` internally: https://docs.rs/http/1.0.0/src/http/header/map.rs.html#1953
166        self.headers.extend(headers);
167        self
168    }
169
170    /// Set the response body with bytes.
171    ///
172    /// It sets "Content-Type" to "application/octet-stream".
173    ///
174    /// To set a body with bytes but a different "Content-Type"
175    /// [`set_body_raw`](#method.set_body_raw) can be used.
176    pub fn set_body_bytes<B>(mut self, body: B) -> Self
177    where
178        B: TryInto<Vec<u8>>,
179        <B as TryInto<Vec<u8>>>::Error: std::fmt::Debug,
180    {
181        let body = body.try_into().expect("Failed to convert into body.");
182        self.body = Some(body);
183        self
184    }
185
186    /// Set the response body from a JSON-serializable value.
187    ///
188    /// It sets "Content-Type" to "application/json".
189    pub fn set_body_json<B: Serialize>(mut self, body: B) -> Self {
190        let body = serde_json::to_vec(&body).expect("Failed to convert into body.");
191
192        self.body = Some(body);
193        self.mime = "application/json".to_string();
194        self
195    }
196
197    /// Set the response body to a string.
198    ///
199    /// It sets "Content-Type" to "text/plain".
200    pub fn set_body_string<T>(mut self, body: T) -> Self
201    where
202        T: TryInto<String>,
203        <T as TryInto<String>>::Error: std::fmt::Debug,
204    {
205        let body = body.try_into().expect("Failed to convert into body.");
206
207        self.body = Some(body.into_bytes());
208        self.mime = "text/plain".to_string();
209        self
210    }
211
212    /// Set a raw response body. The mime type needs to be set because the
213    /// raw body could be of any type.
214    ///
215    /// ### Example:
216    /// ```rust
217    /// use surf::http::mime;
218    /// use wiremock::{MockServer, Mock, ResponseTemplate};
219    /// use wiremock::matchers::method;
220    ///
221    /// mod external {
222    ///     // This could be a method of a struct that is
223    ///     // implemented in another crate and the struct
224    ///     // does not implement Serialize.
225    ///     pub fn body() -> Vec<u8>{
226    ///         r#"{"hello": "world"}"#.as_bytes().to_owned()
227    ///     }
228    /// }
229    ///
230    /// #[async_std::main]
231    /// async fn main() {
232    ///     // Arrange
233    ///     let mock_server = MockServer::start().await;
234    ///     let template = ResponseTemplate::new(200).set_body_raw(
235    ///         external::body(),
236    ///         "application/json"
237    ///     );
238    ///     Mock::given(method("GET"))
239    ///         .respond_with(template)
240    ///         .mount(&mock_server)
241    ///         .await;
242    ///
243    ///     // Act
244    ///     let mut res = surf::get(&mock_server.uri())
245    ///         .await
246    ///         .unwrap();
247    ///     let body = res.body_string()
248    ///         .await
249    ///         .unwrap();
250    ///
251    ///     // Assert
252    ///     assert_eq!(body, r#"{"hello": "world"}"#);
253    ///     assert_eq!(res.content_type(), Some(mime::JSON));
254    /// }
255    /// ```
256    pub fn set_body_raw<B>(mut self, body: B, mime: &str) -> Self
257    where
258        B: TryInto<Vec<u8>>,
259        <B as TryInto<Vec<u8>>>::Error: std::fmt::Debug,
260    {
261        let body = body.try_into().expect("Failed to convert into body.");
262        self.body = Some(body);
263        self.mime = mime.to_string();
264        self
265    }
266
267    /// By default the [`MockServer`] tries to fulfill incoming requests as fast as possible.
268    ///
269    /// You can use `set_delay` to introduce an artificial delay to simulate the behaviour of
270    /// a real server with a non-negligible latency.
271    ///
272    /// In particular, you can use it to test the behaviour of your timeout policies.
273    ///
274    /// ### Example:
275    /// ```rust
276    /// use wiremock::{MockServer, Mock, ResponseTemplate};
277    /// use wiremock::matchers::method;
278    /// use std::time::Duration;
279    /// use async_std::prelude::FutureExt;
280    ///
281    /// #[async_std::main]
282    /// async fn main() {
283    ///     // Arrange
284    ///     let mock_server = MockServer::start().await;
285    ///     let delay = Duration::from_secs(1);
286    ///     let template = ResponseTemplate::new(200).set_delay(delay);
287    ///     Mock::given(method("GET"))
288    ///         .respond_with(template)
289    ///         .mount(&mock_server)
290    ///         .await;
291    ///
292    ///     // Act
293    ///     let mut res = async_std::future::timeout(
294    ///         // Shorter than the response delay!
295    ///         delay / 3,
296    ///         surf::get(&mock_server.uri())
297    ///     )
298    ///     .await;
299    ///
300    ///     // Assert - Timeout error!
301    ///     assert!(res.is_err());
302    /// }
303    /// ```
304    ///
305    /// [`MockServer`]: crate::mock_server::MockServer
306    pub fn set_delay(mut self, delay: Duration) -> Self {
307        self.delay = Some(delay);
308
309        self
310    }
311
312    /// Generate a response from the template.
313    pub(crate) fn generate_response(&self) -> Response<Full<Bytes>> {
314        let mut response = Response::builder().status(self.status_code);
315
316        let mut headers = self.headers.clone();
317        // Set content-type, if needed
318        if !self.mime.is_empty() {
319            headers.insert(http::header::CONTENT_TYPE, self.mime.parse().unwrap());
320        }
321        *response.headers_mut().unwrap() = headers;
322
323        let body = self.body.clone().unwrap_or_default();
324        response.body(body.into()).unwrap()
325    }
326
327    /// Retrieve the response delay.
328    pub(crate) fn delay(&self) -> &Option<Duration> {
329        &self.delay
330    }
331}