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}