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}