mock_http_connector/builder.rs
1use crate::hyper::{
2 http::{self, HeaderName, HeaderValue},
3 Method, Request, Uri,
4};
5use crate::{
6 case::Case,
7 connector::InnerConnector,
8 handler::{DefaultWith, Returning, With, WithHandler},
9 Connector, Error, Level, Report,
10};
11use std::error::Error as StdError;
12
13/// Builder for [`Connector`]
14#[derive(Default)]
15pub struct Builder {
16 inner: InnerConnector,
17}
18
19impl Builder {
20 /// Build into an usable [`Connector`]
21 pub fn build(self) -> Connector {
22 Connector::from_inner(self.inner)
23 }
24
25 /// Set the diagnostics [`Level`] for the connector
26 pub fn level(&mut self, level: Level) {
27 self.inner.level = level;
28 }
29
30 /// Create a new expected case
31 pub fn expect(&mut self) -> CaseBuilder<'_> {
32 CaseBuilder::new(&mut self.inner)
33 }
34}
35
36/// Builder for specific mock cases
37///
38/// ## Example
39///
40/// ```rust
41/// # use mock_http_connector::Connector;
42/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
43/// let mut builder = Connector::builder();
44/// let mut case_builder = builder.expect();
45///
46/// case_builder
47/// .with_uri("https://test.example/some/path")
48/// .times(3)
49/// .returning("Some response")?;
50/// # Ok(())
51/// # }
52/// ```
53#[must_use = "case builders do nothing until you call the `returning` method"]
54pub struct CaseBuilder<'c, W = DefaultWith> {
55 connector: &'c mut InnerConnector,
56 with: Result<W, Error>,
57 count: Option<usize>,
58}
59
60impl<'c> CaseBuilder<'c> {
61 pub(crate) fn new(connector: &'c mut InnerConnector) -> Self {
62 Self {
63 connector,
64 with: Ok(DefaultWith),
65 count: None,
66 }
67 }
68
69 /// Pass a function or closure to check if the incoming payload matches this mock case
70 ///
71 /// If you only need to validate the [`Uri`], [`Method`], headers, or incoming payload, you
72 /// should use one of the other `with_*` methods. You also cannot combine this validator with
73 /// the other `with` methods.
74 ///
75 /// ## Example
76 ///
77 /// ```rust
78 /// # #[cfg(feature = "hyper_0_14")]
79 /// # use hyper_0_14::{Response, Request};
80 /// # #[cfg(feature = "hyper_1")]
81 /// # use hyper_1::{Response, Request};
82 /// # use mock_http_connector::{Connector, Error};
83 /// # use std::convert::Infallible;
84 /// # || {
85 /// let mut builder = Connector::builder();
86 /// builder
87 /// .expect()
88 /// .with(|req: &Request<String>| Ok::<_, Infallible>(req.body().contains("hello")))
89 /// .returning("OK")?;
90 /// # Ok::<_, Error>(())
91 /// # };
92 /// ```
93 pub fn with<W, E, R>(self, with: W) -> CaseBuilder<'c, W>
94 where
95 for<'r> W: Fn(&'r Request<String>) -> Result<R, E>,
96 R: Into<Report>,
97 E: StdError + Send + Sync + 'static,
98 {
99 CaseBuilder {
100 connector: self.connector,
101 with: Ok(with),
102 count: self.count,
103 }
104 }
105
106 /// Match requests with the specified [`Uri`]
107 ///
108 /// ## Example
109 ///
110 /// ```rust
111 /// # #[cfg(feature = "hyper_0_14")]
112 /// # use hyper_0_14::Response;
113 /// # #[cfg(feature = "hyper_1")]
114 /// # use hyper_1::Response;
115 /// # use mock_http_connector::{Connector, Error};
116 /// # || {
117 /// let mut builder = Connector::builder();
118 /// builder
119 /// .expect()
120 /// .with_uri("https://example.test/hello")
121 /// .returning("OK")?;
122 /// # Ok::<_, Error>(())
123 /// # };
124 /// ```
125 ///
126 /// ## Remark
127 ///
128 /// You can combine this with other validators, such as `with_header`, but not with `with`.
129 pub fn with_uri<U>(self, uri: U) -> CaseBuilder<'c, WithHandler>
130 where
131 U: TryInto<Uri>,
132 U::Error: Into<http::Error>,
133 {
134 CaseBuilder {
135 connector: self.connector,
136 with: WithHandler::default().with_uri(uri),
137 count: self.count,
138 }
139 }
140
141 /// Match requests with the specified [`Method`]
142 ///
143 /// ## Example
144 ///
145 /// ```rust
146 /// # #[cfg(feature = "hyper_0_14")]
147 /// # use hyper_0_14::Response;
148 /// # #[cfg(feature = "hyper_1")]
149 /// # use hyper_1::Response;
150 /// # use mock_http_connector::{Connector, Error};
151 /// # || {
152 /// let mut builder = Connector::builder();
153 /// builder
154 /// .expect()
155 /// .with_method("GET")
156 /// .returning("OK")?;
157 /// # Ok::<_, Error>(())
158 /// # };
159 /// ```
160 ///
161 /// ## Remark
162 ///
163 /// You can combine this with other validators, such as `with_uri`, but not with `with`.
164 pub fn with_method<M>(self, method: M) -> CaseBuilder<'c, WithHandler>
165 where
166 M: TryInto<Method>,
167 M::Error: Into<http::Error>,
168 {
169 CaseBuilder {
170 connector: self.connector,
171 with: WithHandler::default().with_method(method),
172 count: self.count,
173 }
174 }
175
176 /// Match requests that contains the specific header
177 ///
178 /// An HTTP request can contain multiple headers with the same key, but different values. This
179 /// checks that there is at least one value matching. If you want to ensure that there is only
180 /// one entry for this key, consider using `with_header_once`.
181 ///
182 /// ## Example
183 ///
184 /// ```rust
185 /// # #[cfg(feature = "hyper_0_14")]
186 /// # use hyper_0_14::Response;
187 /// # #[cfg(feature = "hyper_1")]
188 /// # use hyper_1::Response;
189 /// # use mock_http_connector::{Connector, Error};
190 /// # || {
191 /// let mut builder = Connector::builder();
192 /// builder
193 /// .expect()
194 /// .with_header("content-type", "application/json")
195 /// .returning("OK")?;
196 /// # Ok::<_, Error>(())
197 /// # };
198 /// ```
199 ///
200 /// ## Remark
201 ///
202 /// You can combine this with other validators, such as `with_uri`, but not with `with`.
203 pub fn with_header<K, V>(self, key: K, value: V) -> CaseBuilder<'c, WithHandler>
204 where
205 K: TryInto<HeaderName>,
206 K::Error: Into<http::Error>,
207 V: TryInto<HeaderValue>,
208 V::Error: Into<http::Error>,
209 {
210 CaseBuilder {
211 connector: self.connector,
212 with: WithHandler::default().with_header(key, value),
213 count: self.count,
214 }
215 }
216
217 /// Match requests that contains the specific header
218 ///
219 /// An HTTP request can contain multiple headers with the same key, but different values. This
220 /// checks that there is only one value for the given header.
221 ///
222 /// ## Example
223 ///
224 /// ```rust
225 /// # #[cfg(feature = "hyper_0_14")]
226 /// # use hyper_0_14::Response;
227 /// # #[cfg(feature = "hyper_1")]
228 /// # use hyper_1::Response;
229 /// # use mock_http_connector::{Connector, Error};
230 /// # || {
231 /// let mut builder = Connector::builder();
232 /// builder
233 /// .expect()
234 /// .with_header_once("content-type", "application/json")
235 /// .returning("OK")?;
236 /// # Ok::<_, Error>(())
237 /// # };
238 /// ```
239 ///
240 /// ## Remark
241 ///
242 /// You can combine this with other validators, such as `with_uri`, but not with `with`.
243 pub fn with_header_once<K, V>(self, key: K, value: V) -> CaseBuilder<'c, WithHandler>
244 where
245 K: TryInto<HeaderName>,
246 K::Error: Into<http::Error>,
247 V: TryInto<HeaderValue>,
248 V::Error: Into<http::Error>,
249 {
250 CaseBuilder {
251 connector: self.connector,
252 with: WithHandler::default().with_header_once(key, value),
253 count: self.count,
254 }
255 }
256
257 /// Match requests that contains the specific header
258 ///
259 /// An HTTP request can contain multiple headers with the same key, but different values. This
260 /// checks that all entries correspond to the given set of values.
261 ///
262 /// If you want to check that a header name has multiple values, but do not mind if there are
263 /// additional values, you can use `with_header` multiple times instead.
264 ///
265 /// If you want to ensure that a header name only has one value, you can use `with_header_once`
266 /// instead.
267 ///
268 /// ## Example
269 ///
270 /// ```rust
271 /// # #[cfg(feature = "hyper_0_14")]
272 /// # use hyper_0_14::Response;
273 /// # #[cfg(feature = "hyper_1")]
274 /// # use hyper_1::Response;
275 /// # use mock_http_connector::{Connector, Error};
276 /// # || {
277 /// let mut builder = Connector::builder();
278 /// builder
279 /// .expect()
280 /// .with_header_all("content-type", ["application/json", "text/html"])
281 /// .returning("OK")?;
282 /// # Ok::<_, Error>(())
283 /// # };
284 /// ```
285 ///
286 /// ## Remark
287 ///
288 /// You can combine this with other validators, such as `with_uri`, but not with `with`.
289 pub fn with_header_all<K, IV, V>(self, key: K, values: IV) -> CaseBuilder<'c, WithHandler>
290 where
291 K: TryInto<HeaderName>,
292 K::Error: Into<http::Error>,
293 IV: IntoIterator<Item = V>,
294 V: TryInto<HeaderValue>,
295 V::Error: Into<http::Error>,
296 {
297 CaseBuilder {
298 connector: self.connector,
299 with: WithHandler::default().with_header_all(key, values),
300 count: self.count,
301 }
302 }
303
304 /// Match requests that contains the provided payload
305 ///
306 /// ## Example
307 ///
308 /// ```rust
309 /// # #[cfg(feature = "hyper_0_14")]
310 /// # use hyper_0_14::Response;
311 /// # #[cfg(feature = "hyper_1")]
312 /// # use hyper_1::Response;
313 /// # use mock_http_connector::{Connector, Error};
314 /// # || {
315 /// let mut builder = Connector::builder();
316 /// builder
317 /// .expect()
318 /// .with_body("some body")
319 /// .returning("OK")?;
320 /// # Ok::<_, Error>(())
321 /// # };
322 /// ```
323 ///
324 /// ## Remark
325 ///
326 /// You can combine this with other validators, such as `with_uri`, but not with `with`.
327 ///
328 /// A mock case only supports `with_body`, `with_json`, or `with_json_value`, but not multiple
329 /// ones at the same time.
330 pub fn with_body<B>(self, body: B) -> CaseBuilder<'c, WithHandler>
331 where
332 B: ToString,
333 {
334 CaseBuilder {
335 connector: self.connector,
336 with: Ok(WithHandler::default().with_body(body)),
337 count: self.count,
338 }
339 }
340
341 /// Match requests with a body that exactly matches the provided JSON payload
342 ///
343 /// ## Example
344 ///
345 /// ```rust
346 /// # #[cfg(feature = "hyper_0_14")]
347 /// # use hyper_0_14::Response;
348 /// # #[cfg(feature = "hyper_1")]
349 /// # use hyper_1::Response;
350 /// # use mock_http_connector::{Connector, Error};
351 /// # || {
352 /// let mut builder = Connector::builder();
353 /// builder
354 /// .expect()
355 /// .with_json(serde_json::json!({"status": "OK"}))
356 /// .returning("OK")?;
357 /// # Ok::<_, Error>(())
358 /// # };
359 /// ```
360 ///
361 /// ## Remark
362 ///
363 /// You can combine this with other validators, such as `with_uri`, but not with `with`.
364 ///
365 /// A mock case only supports `with_body`, `with_json`, or `with_json_value`, but not multiple
366 /// ones at the same time.
367 #[cfg(feature = "json")]
368 pub fn with_json<V>(self, value: V) -> CaseBuilder<'c, WithHandler>
369 where
370 V: serde::Serialize,
371 {
372 CaseBuilder {
373 connector: self.connector,
374 with: WithHandler::default().with_json(value),
375 count: self.count,
376 }
377 }
378
379 /// Match requests that contains the provided JSON payload, but may contain other properties
380 ///
381 /// You can combine this with other validators, such as `with_uri`, but not with `with`.
382 #[cfg(feature = "json")]
383 pub fn with_json_partial<V>(self, value: V) -> CaseBuilder<'c, WithHandler>
384 where
385 V: serde::Serialize,
386 {
387 CaseBuilder {
388 connector: self.connector,
389 with: WithHandler::default().with_json_partial(value),
390 count: self.count,
391 }
392 }
393}
394
395impl CaseBuilder<'_, WithHandler> {
396 #[doc(hidden)]
397 pub fn with_uri<U>(mut self, uri: U) -> Self
398 where
399 U: TryInto<Uri>,
400 U::Error: Into<http::Error>,
401 {
402 self.with = self.with.and_then(|w| w.with_uri(uri));
403 self
404 }
405
406 #[doc(hidden)]
407 pub fn with_method<M>(mut self, method: M) -> Self
408 where
409 M: TryInto<Method>,
410 M::Error: Into<http::Error>,
411 {
412 self.with = self.with.and_then(|w| w.with_method(method));
413 self
414 }
415
416 #[doc(hidden)]
417 pub fn with_header<K, V>(mut self, key: K, value: V) -> Self
418 where
419 K: TryInto<HeaderName>,
420 K::Error: Into<http::Error>,
421 V: TryInto<HeaderValue>,
422 V::Error: Into<http::Error>,
423 {
424 self.with = self.with.and_then(|w| w.with_header(key, value));
425 self
426 }
427
428 #[doc(hidden)]
429 pub fn with_header_once<K, V>(mut self, key: K, value: V) -> Self
430 where
431 K: TryInto<HeaderName>,
432 K::Error: Into<http::Error>,
433 V: TryInto<HeaderValue>,
434 V::Error: Into<http::Error>,
435 {
436 self.with = self.with.and_then(|w| w.with_header_once(key, value));
437 self
438 }
439
440 #[doc(hidden)]
441 pub fn with_header_all<K, IV, V>(mut self, key: K, values: IV) -> Self
442 where
443 K: TryInto<HeaderName>,
444 K::Error: Into<http::Error>,
445 IV: IntoIterator<Item = V>,
446 V: TryInto<HeaderValue>,
447 V::Error: Into<http::Error>,
448 {
449 self.with = self.with.and_then(|w| w.with_header_all(key, values));
450 self
451 }
452
453 #[doc(hidden)]
454 pub fn with_body<B>(mut self, body: B) -> Self
455 where
456 B: ToString,
457 {
458 self.with = self.with.map(|w| w.with_body(body));
459 self
460 }
461
462 #[doc(hidden)]
463 #[cfg(feature = "json")]
464 pub fn with_json<V>(mut self, value: V) -> Self
465 where
466 V: serde::Serialize,
467 {
468 self.with = self.with.and_then(|w| w.with_json(value));
469 self
470 }
471
472 #[doc(hidden)]
473 #[cfg(feature = "json")]
474 pub fn with_json_partial<V>(mut self, value: V) -> Self
475 where
476 V: serde::Serialize,
477 {
478 self.with = self.with.and_then(|w| w.with_json_partial(value));
479 self
480 }
481}
482
483impl<W> CaseBuilder<'_, W> {
484 /// Mark how many times this mock case can be called
485 ///
486 /// Nothing enforces how many times a mock case is called, but you can use the `checkpoint`
487 /// method on the [`Connector`] to ensure all methods were called the right amount of times.
488 pub fn times(self, count: usize) -> Self {
489 Self {
490 count: Some(count),
491 ..self
492 }
493 }
494}
495
496impl<W> CaseBuilder<'_, W>
497where
498 W: With + 'static,
499{
500 /// Mark what will generate the response for a given mock case
501 ///
502 /// You can either pass a static value, or a function or closure that takes a `Request<String>`
503 /// as an input.
504 ///
505 /// See the documentation for [`Returning`] to see the full list of what is accepted by this
506 /// method.
507 ///
508 /// ## Errors
509 ///
510 /// This will fail if any of the previous steps in [`CaseBuilder`] failed, or if it fails to
511 /// store the case into the connector.
512 pub fn returning<R>(self, returning: R) -> Result<(), Error>
513 where
514 R: Returning + 'static,
515 {
516 let case = Case::new(self.with?, returning, self.count);
517 self.connector.cases.push(case);
518
519 Ok(())
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use std::convert::Infallible;
526
527 use crate::Connector;
528
529 use super::*;
530
531 #[test]
532 fn test_with() {
533 let mut connector = Connector::builder();
534 connector
535 .expect()
536 .with(|req: &Request<String>| Ok::<_, Infallible>(req.body().contains("hello")))
537 .returning("OK")
538 .unwrap();
539 }
540}