Skip to content

Commit f612e93

Browse files
committed
Merge remote-tracking branch 'origin/main' into handle_form_data
2 parents 7bef356 + c1710b4 commit f612e93

17 files changed

+439
-332
lines changed

Cargo.toml

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "wiremock"
3-
version = "0.5.19"
3+
version = "0.6.0"
44
authors = ["Luca Palmieri <[email protected]>"]
55
edition = "2018"
66

@@ -20,19 +20,21 @@ path = "src/lib.rs"
2020

2121
[dependencies]
2222
log = "0.4"
23-
http-types = { version = "2.11", default-features = false, features = ["hyperium_http"] }
2423
serde_json = "1"
2524
serde = "1"
2625
regex = "1"
27-
futures-timer = "3.0.2"
2826
futures = "0.3.5"
29-
hyper = { version = "0.14", features = ["full"] }
30-
tokio = { version = "1.5.0", features = ["rt"] }
31-
deadpool = "0.9.2"
27+
http = "1.0"
28+
http-body-util = "0.1"
29+
hyper = { version = "1.0", features = ["full"] }
30+
hyper-util = { version = "0.1", features = ["tokio"] }
31+
tokio = { version = "1.5.0", features = ["rt", "macros"] }
32+
deadpool = "0.10.0"
3233
async-trait = "0.1"
3334
once_cell = "1"
3435
assert-json-diff = "2.0.1"
3536
base64 = "0.21.0"
37+
url = "2.2"
3638
serde_urlencoded = "0.7.1"
3739

3840
[dev-dependencies]
@@ -41,4 +43,3 @@ surf = "2.3.2"
4143
reqwest = "0.11.3"
4244
tokio = { version = "1.5.0", features = ["macros", "rt-multi-thread"] }
4345
actix-rt = "2.2.0"
44-
isahc = "1.3.1"

src/http.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
//! Convenient re-exports of `http-types`' types that are part of `wiremock`'s public API.
2-
pub use http_types::headers::{HeaderName, HeaderValue, HeaderValues};
3-
pub use http_types::{Method, Url};
1+
//! Convenient re-exports of http types that are part of `wiremock`'s public API.
2+
pub use http::{HeaderMap, HeaderName, HeaderValue, Method};
3+
pub use url::Url;

src/matchers.rs

+99-35
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@
1010
use crate::{Match, Request};
1111
use assert_json_diff::{assert_json_matches_no_panic, CompareMode};
1212
use base64::prelude::{Engine as _, BASE64_STANDARD};
13-
use http_types::headers::{HeaderName, HeaderValue, HeaderValues};
14-
use http_types::{Method, Url};
13+
use http::{HeaderName, HeaderValue, Method};
1514
use log::debug;
1615
use regex::Regex;
1716
use serde::Serialize;
1817
use serde_json::Value;
1918
use std::convert::TryInto;
20-
use std::ops::Deref;
2119
use std::str;
20+
use url::Url;
2221

2322
/// Implement the `Match` trait for all closures, out of the box,
2423
/// if their signature is compatible.
@@ -342,7 +341,7 @@ impl Match for PathRegexMatcher {
342341
/// assert_eq!(status, 200);
343342
/// }
344343
/// ```
345-
pub struct HeaderExactMatcher(HeaderName, HeaderValues);
344+
pub struct HeaderExactMatcher(HeaderName, Vec<HeaderValue>);
346345

347346
/// Shorthand for [`HeaderExactMatcher::new`].
348347
pub fn header<K, V>(key: K, value: V) -> HeaderExactMatcher
@@ -352,7 +351,7 @@ where
352351
V: TryInto<HeaderValue>,
353352
<V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
354353
{
355-
HeaderExactMatcher::new(key, value.try_into().map(HeaderValues::from).unwrap())
354+
HeaderExactMatcher::new(key, vec![value])
356355
}
357356

358357
/// Shorthand for [`HeaderExactMatcher::new`] supporting multi valued headers.
@@ -363,38 +362,44 @@ where
363362
V: TryInto<HeaderValue>,
364363
<V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
365364
{
366-
let values = values
367-
.into_iter()
368-
.filter_map(|v| v.try_into().ok())
369-
.collect::<HeaderValues>();
370365
HeaderExactMatcher::new(key, values)
371366
}
372367

373368
impl HeaderExactMatcher {
374-
pub fn new<K, V>(key: K, value: V) -> Self
369+
pub fn new<K, V>(key: K, values: Vec<V>) -> Self
375370
where
376371
K: TryInto<HeaderName>,
377372
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
378-
V: TryInto<HeaderValues>,
379-
<V as TryInto<HeaderValues>>::Error: std::fmt::Debug,
373+
V: TryInto<HeaderValue>,
374+
<V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
380375
{
381376
let key = key.try_into().expect("Failed to convert to header name.");
382-
let value = value
383-
.try_into()
384-
.expect("Failed to convert to header value.");
385-
Self(key, value)
377+
let values = values
378+
.into_iter()
379+
.map(|value| {
380+
value
381+
.try_into()
382+
.expect("Failed to convert to header value.")
383+
})
384+
.collect();
385+
Self(key, values)
386386
}
387387
}
388388

389389
impl Match for HeaderExactMatcher {
390390
fn matches(&self, request: &Request) -> bool {
391-
match request.headers.get(&self.0) {
392-
None => false,
393-
Some(values) => {
394-
let headers: Vec<&str> = self.1.iter().map(HeaderValue::as_str).collect();
395-
values.eq(headers.as_slice())
396-
}
397-
}
391+
let values = request
392+
.headers
393+
.get_all(&self.0)
394+
.iter()
395+
.filter_map(|v| v.to_str().ok())
396+
.flat_map(|v| {
397+
v.split(',')
398+
.map(str::trim)
399+
.filter_map(|v| HeaderValue::from_str(v).ok())
400+
})
401+
.collect::<Vec<_>>();
402+
values == self.1 // order matters
398403
}
399404
}
400405

@@ -513,12 +518,16 @@ impl HeaderRegexMatcher {
513518

514519
impl Match for HeaderRegexMatcher {
515520
fn matches(&self, request: &Request) -> bool {
516-
match request.headers.get(&self.0) {
517-
None => false,
518-
Some(values) => {
519-
let has_values = values.iter().next().is_some();
520-
has_values && values.iter().all(|v| self.1.is_match(v.as_str()))
521-
}
521+
let mut it = request
522+
.headers
523+
.get_all(&self.0)
524+
.iter()
525+
.filter_map(|v| v.to_str().ok())
526+
.peekable();
527+
if it.peek().is_some() {
528+
it.all(|v| self.1.is_match(v))
529+
} else {
530+
false
522531
}
523532
}
524533
}
@@ -861,6 +870,64 @@ impl Match for QueryParamExactMatcher {
861870
}
862871
}
863872

873+
#[derive(Debug)]
874+
/// Match when a query parameter contains the specified value as a substring.
875+
///
876+
/// ### Example:
877+
/// ```rust
878+
/// use wiremock::{MockServer, Mock, ResponseTemplate};
879+
/// use wiremock::matchers::query_param_contains;
880+
///
881+
/// #[async_std::main]
882+
/// async fn main() {
883+
/// // Arrange
884+
/// let mock_server = MockServer::start().await;
885+
///
886+
/// // It matches since "world" is a substring of "some_world".
887+
/// Mock::given(query_param_contains("hello", "world"))
888+
/// .respond_with(ResponseTemplate::new(200))
889+
/// .mount(&mock_server)
890+
/// .await;
891+
///
892+
/// // Act
893+
/// let status = surf::get(format!("{}?hello=some_world", &mock_server.uri()))
894+
/// .await
895+
/// .unwrap()
896+
/// .status();
897+
///
898+
/// // Assert
899+
/// assert_eq!(status, 200);
900+
/// }
901+
/// ```
902+
pub struct QueryParamContainsMatcher(String, String);
903+
904+
impl QueryParamContainsMatcher {
905+
/// Specify the substring that the query parameter should contain.
906+
pub fn new<K: Into<String>, V: Into<String>>(key: K, value: V) -> Self {
907+
let key = key.into();
908+
let value = value.into();
909+
Self(key, value)
910+
}
911+
}
912+
913+
/// Shorthand for [`QueryParamContainsMatcher::new`].
914+
pub fn query_param_contains<K, V>(key: K, value: V) -> QueryParamContainsMatcher
915+
where
916+
K: Into<String>,
917+
V: Into<String>,
918+
{
919+
QueryParamContainsMatcher::new(key, value)
920+
}
921+
922+
impl Match for QueryParamContainsMatcher {
923+
fn matches(&self, request: &Request) -> bool {
924+
request
925+
.url
926+
.query_pairs()
927+
.any(|q| q.0 == self.0.as_str() && q.1.contains(self.1.as_str()))
928+
}
929+
}
930+
864931
#[derive(Debug)]
865932
/// Only match requests that do **not** contain a specified query parameter.
866933
///
@@ -994,7 +1061,6 @@ where
9941061
/// use wiremock::{MockServer, Mock, ResponseTemplate};
9951062
/// use wiremock::matchers::basic_auth;
9961063
/// use serde::{Deserialize, Serialize};
997-
/// use http_types::auth::BasicAuth;
9981064
/// use std::convert::TryInto;
9991065
///
10001066
/// #[async_std::main]
@@ -1008,10 +1074,9 @@ where
10081074
/// .mount(&mock_server)
10091075
/// .await;
10101076
///
1011-
/// let auth = BasicAuth::new("username", "password");
10121077
/// let client: surf::Client = surf::Config::new()
10131078
/// .set_base_url(surf::Url::parse(&mock_server.uri()).unwrap())
1014-
/// .add_header(auth.name(), auth.value()).unwrap()
1079+
/// .add_header("Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=").unwrap()
10151080
/// .try_into().unwrap();
10161081
///
10171082
/// // Act
@@ -1040,7 +1105,7 @@ impl BasicAuthMatcher {
10401105
pub fn from_token(token: impl AsRef<str>) -> Self {
10411106
Self(header(
10421107
"Authorization",
1043-
format!("Basic {}", token.as_ref()).deref(),
1108+
&*format!("Basic {}", token.as_ref()),
10441109
))
10451110
}
10461111
}
@@ -1069,7 +1134,6 @@ impl Match for BasicAuthMatcher {
10691134
/// use wiremock::{MockServer, Mock, ResponseTemplate};
10701135
/// use wiremock::matchers::bearer_token;
10711136
/// use serde::{Deserialize, Serialize};
1072-
/// use http_types::auth::BasicAuth;
10731137
///
10741138
/// #[async_std::main]
10751139
/// async fn main() {
@@ -1098,7 +1162,7 @@ impl BearerTokenMatcher {
10981162
pub fn from_token(token: impl AsRef<str>) -> Self {
10991163
Self(header(
11001164
"Authorization",
1101-
format!("Bearer {}", token.as_ref()).deref(),
1165+
&*format!("Bearer {}", token.as_ref()),
11021166
))
11031167
}
11041168
}

src/mock.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ use std::ops::{
1515
/// use std::convert::TryInto;
1616
///
1717
/// // Check that a header with the specified name exists and its value has an odd length.
18-
/// pub struct OddHeaderMatcher(http_types::headers::HeaderName);
18+
/// pub struct OddHeaderMatcher(http::HeaderName);
1919
///
2020
/// impl Match for OddHeaderMatcher {
2121
/// fn matches(&self, request: &Request) -> bool {
2222
/// match request.headers.get(&self.0) {
2323
/// // We are ignoring multi-valued headers for simplicity
24-
/// Some(values) => values[0].as_str().len() % 2 == 1,
24+
/// Some(value) => value.to_str().unwrap_or_default().len() % 2 == 1,
2525
/// None => false
2626
/// }
2727
/// }
@@ -69,11 +69,11 @@ use std::ops::{
6969
/// // Arrange
7070
/// let mock_server = MockServer::start().await;
7171
///
72-
/// let header_name: http_types::headers::HeaderName = "custom".try_into().unwrap();
72+
/// let header_name = http::HeaderName::from_static("custom");
7373
/// // Check that a header with the specified name exists and its value has an odd length.
7474
/// let matcher = move |request: &Request| {
7575
/// match request.headers.get(&header_name) {
76-
/// Some(values) => values[0].as_str().len() % 2 == 1,
76+
/// Some(value) => value.to_str().unwrap_or_default().len() % 2 == 1,
7777
/// None => false
7878
/// }
7979
/// };
@@ -160,7 +160,7 @@ impl Debug for Matcher {
160160
/// mock_server.register(mock).await;
161161
///
162162
/// // We won't register this mock instead.
163-
/// let unregistered_mock = Mock::given(method("GET")).respond_with(response);
163+
/// let unregistered_mock = Mock::given(method("POST")).respond_with(response);
164164
///
165165
/// // Act
166166
/// let status = surf::get(&mock_server.uri())
@@ -253,6 +253,7 @@ impl Debug for Matcher {
253253
/// [`register`]: MockServer::register
254254
/// [`mount`]: Mock::mount
255255
/// [`mount_as_scoped`]: Mock::mount_as_scoped
256+
#[must_use = "`Mock`s have to be mounted or registered with a `MockServer` to become effective"]
256257
pub struct Mock {
257258
pub(crate) matchers: Vec<Matcher>,
258259
pub(crate) response: Box<dyn Respond>,
@@ -539,7 +540,7 @@ impl Mock {
539540
/// # Limitations
540541
///
541542
/// When expectations of a scoped [`Mock`] are not verified, it will trigger a panic - just like a normal [`Mock`].
542-
/// Due to [limitations](https://internals.rust-lang.org/t/should-drop-glue-use-track-caller/13682) in Rust's [`Drop`](std::ops::Drop) trait,
543+
/// Due to [limitations](https://internals.rust-lang.org/t/should-drop-glue-use-track-caller/13682) in Rust's [`Drop`] trait,
543544
/// the panic message will not include the filename and the line location
544545
/// where the corresponding [`MockGuard`] was dropped - it will point into `wiremock`'s source code.
545546
///
@@ -626,7 +627,7 @@ impl Mock {
626627
server.register_as_scoped(self).await
627628
}
628629

629-
/// Given a [`Request`](crate::Request) build an instance a [`ResponseTemplate`] using
630+
/// Given a [`Request`] build an instance a [`ResponseTemplate`] using
630631
/// the responder associated with the `Mock`.
631632
pub(crate) fn response_template(&self, request: &Request) -> ResponseTemplate {
632633
self.response.respond(request)

0 commit comments

Comments
 (0)