use crate::{Match, Request};
use assert_json_diff::{assert_json_matches_no_panic, CompareMode};
use http_types::headers::{HeaderName, HeaderValue, HeaderValues};
use http_types::{Method, Url};
use log::debug;
use regex::Regex;
use serde::Serialize;
use serde_json::Value;
use std::convert::TryInto;
use std::ops::Deref;
use std::str;
impl<F> Match for F
where
F: Fn(&Request) -> bool,
F: Send + Sync,
{
fn matches(&self, request: &Request) -> bool {
self(request)
}
}
#[derive(Debug)]
pub struct MethodExactMatcher(Method);
pub fn method<T>(method: T) -> MethodExactMatcher
where
T: TryInto<Method>,
<T as TryInto<Method>>::Error: std::fmt::Debug,
{
MethodExactMatcher::new(method)
}
impl MethodExactMatcher {
pub fn new<T>(method: T) -> Self
where
T: TryInto<Method>,
<T as TryInto<Method>>::Error: std::fmt::Debug,
{
let method = method
.try_into()
.expect("Failed to convert to HTTP method.");
Self(method)
}
}
impl Match for MethodExactMatcher {
fn matches(&self, request: &Request) -> bool {
request.method == self.0
}
}
#[derive(Debug)]
pub struct AnyMatcher;
pub fn any() -> AnyMatcher {
AnyMatcher
}
impl Match for AnyMatcher {
fn matches(&self, _request: &Request) -> bool {
true
}
}
#[derive(Debug)]
pub struct PathExactMatcher(String);
pub fn path<T>(path: T) -> PathExactMatcher
where
T: Into<String>,
{
PathExactMatcher::new(path)
}
impl PathExactMatcher {
pub fn new<T: Into<String>>(path: T) -> Self {
let path = path.into();
if path.contains('?') {
panic!("Wiremock can't match the path `{}` because it contains a `?`. You must use `wiremock::matchers::query_param` to match on query parameters (the part of the path after the `?`).", path);
}
if let Ok(url) = Url::parse(&path) {
if let Some(host) = url.host_str() {
panic!("Wiremock can't match the path `{}` because it contains the host `{}`. You don't have to specify the host - wiremock knows it. Try replacing your path with `path(\"{}\")`", path, host, url.path());
}
}
if path.starts_with('/') {
Self(path)
} else {
Self(format!("/{}", path))
}
}
}
impl Match for PathExactMatcher {
fn matches(&self, request: &Request) -> bool {
request.url.path() == self.0
}
}
#[derive(Debug)]
pub struct PathRegexMatcher(Regex);
pub fn path_regex<T>(path: T) -> PathRegexMatcher
where
T: Into<String>,
{
PathRegexMatcher::new(path)
}
impl PathRegexMatcher {
pub fn new<T: Into<String>>(path: T) -> Self {
let path = path.into();
Self(Regex::new(&path).expect("Failed to create regex for path matcher"))
}
}
impl Match for PathRegexMatcher {
fn matches(&self, request: &Request) -> bool {
self.0.is_match(request.url.path())
}
}
#[derive(Debug)]
pub struct HeaderExactMatcher(HeaderName, HeaderValues);
pub fn header<K, V>(key: K, value: V) -> HeaderExactMatcher
where
K: TryInto<HeaderName>,
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
V: TryInto<HeaderValue>,
<V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
{
HeaderExactMatcher::new(key, value.try_into().map(HeaderValues::from).unwrap())
}
pub fn headers<K, V>(key: K, values: Vec<V>) -> HeaderExactMatcher
where
K: TryInto<HeaderName>,
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
V: TryInto<HeaderValue>,
<V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
{
let values = values
.into_iter()
.filter_map(|v| v.try_into().ok())
.collect::<HeaderValues>();
HeaderExactMatcher::new(key, values)
}
impl HeaderExactMatcher {
pub fn new<K, V>(key: K, value: V) -> Self
where
K: TryInto<HeaderName>,
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
V: TryInto<HeaderValues>,
<V as TryInto<HeaderValues>>::Error: std::fmt::Debug,
{
let key = key.try_into().expect("Failed to convert to header name.");
let value = value
.try_into()
.expect("Failed to convert to header value.");
Self(key, value)
}
}
impl Match for HeaderExactMatcher {
fn matches(&self, request: &Request) -> bool {
match request.headers.get(&self.0) {
None => false,
Some(values) => {
let headers: Vec<&str> = self.1.iter().map(HeaderValue::as_str).collect();
values.eq(headers.as_slice())
}
}
}
}
#[derive(Debug)]
pub struct HeaderExistsMatcher(HeaderName);
pub fn header_exists<K>(key: K) -> HeaderExistsMatcher
where
K: TryInto<HeaderName>,
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
{
HeaderExistsMatcher::new(key)
}
impl HeaderExistsMatcher {
pub fn new<K>(key: K) -> Self
where
K: TryInto<HeaderName>,
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
{
let key = key.try_into().expect("Failed to convert to header name.");
Self(key)
}
}
impl Match for HeaderExistsMatcher {
fn matches(&self, request: &Request) -> bool {
request.headers.get(&self.0).is_some()
}
}
#[derive(Debug)]
pub struct HeaderRegexMatcher(HeaderName, Regex);
pub fn header_regex<K>(key: K, value: &str) -> HeaderRegexMatcher
where
K: TryInto<HeaderName>,
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
{
HeaderRegexMatcher::new(key, value)
}
impl HeaderRegexMatcher {
pub fn new<K>(key: K, value: &str) -> Self
where
K: TryInto<HeaderName>,
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
{
let key = key.try_into().expect("Failed to convert to header name.");
let value_matcher = Regex::new(value).expect("Failed to create regex for value matcher");
Self(key, value_matcher)
}
}
impl Match for HeaderRegexMatcher {
fn matches(&self, request: &Request) -> bool {
match request.headers.get(&self.0) {
None => false,
Some(values) => {
let has_values = values.iter().next().is_some();
has_values && values.iter().all(|v| self.1.is_match(v.as_str()))
}
}
}
}
#[derive(Debug)]
pub struct BodyExactMatcher(Body);
#[derive(Debug)]
enum Body {
Bytes(Vec<u8>),
Json(Value),
}
impl BodyExactMatcher {
pub fn string<T: Into<String>>(body: T) -> Self {
let body = body.into();
Self(Body::Bytes(body.into_bytes()))
}
pub fn bytes<T: Into<Vec<u8>>>(body: T) -> Self {
let body = body.into();
Self(Body::Bytes(body))
}
pub fn json<T: Serialize>(body: T) -> Self {
let bytes = serde_json::to_vec(&body).expect("Failed to serialize JSON body");
Self::json_string(bytes)
}
pub fn json_string(body: impl AsRef<[u8]>) -> Self {
let body = serde_json::from_slice(body.as_ref()).expect("Failed to parse JSON string");
Self(Body::Json(body))
}
}
pub fn body_string<T>(body: T) -> BodyExactMatcher
where
T: Into<String>,
{
BodyExactMatcher::string(body)
}
pub fn body_bytes<T>(body: T) -> BodyExactMatcher
where
T: Into<Vec<u8>>,
{
BodyExactMatcher::bytes(body)
}
pub fn body_json<T>(body: T) -> BodyExactMatcher
where
T: Serialize,
{
BodyExactMatcher::json(body)
}
pub fn body_json_string(body: impl AsRef<[u8]>) -> BodyExactMatcher {
BodyExactMatcher::json_string(body)
}
impl Match for BodyExactMatcher {
fn matches(&self, request: &Request) -> bool {
match &self.0 {
Body::Bytes(bytes) => request.body == *bytes,
Body::Json(json) => {
if let Ok(body) = serde_json::from_slice::<Value>(&request.body) {
body == *json
} else {
false
}
}
}
}
}
#[derive(Debug)]
pub struct BodyContainsMatcher(Vec<u8>);
impl BodyContainsMatcher {
pub fn string<T: Into<String>>(body: T) -> Self {
Self(body.into().as_bytes().into())
}
}
pub fn body_string_contains<T>(body: T) -> BodyContainsMatcher
where
T: Into<String>,
{
BodyContainsMatcher::string(body)
}
impl Match for BodyContainsMatcher {
fn matches(&self, request: &Request) -> bool {
let body = match str::from_utf8(&request.body) {
Ok(body) => body.to_string(),
Err(err) => {
debug!("can't convert body from byte slice to string: {}", err);
return false;
}
};
let part = match str::from_utf8(&self.0) {
Ok(part) => part,
Err(err) => {
debug!(
"can't convert expected part from byte slice to string: {}",
err
);
return false;
}
};
body.contains(part)
}
}
#[derive(Debug)]
pub struct BodyPartialJsonMatcher(Value);
impl BodyPartialJsonMatcher {
pub fn json<T: Serialize>(body: T) -> Self {
Self(serde_json::to_value(body).expect("Can't serialize to JSON"))
}
pub fn json_string(body: impl AsRef<str>) -> Self {
Self(serde_json::from_str(body.as_ref()).expect("Can't deserialize JSON"))
}
}
pub fn body_partial_json<T: Serialize>(body: T) -> BodyPartialJsonMatcher {
BodyPartialJsonMatcher::json(body)
}
pub fn body_partial_json_string(body: impl AsRef<str>) -> BodyPartialJsonMatcher {
BodyPartialJsonMatcher::json_string(body)
}
impl Match for BodyPartialJsonMatcher {
fn matches(&self, request: &Request) -> bool {
if let Ok(body) = serde_json::from_slice::<Value>(&request.body) {
let config = assert_json_diff::Config::new(CompareMode::Inclusive);
assert_json_matches_no_panic(&body, &self.0, config).is_ok()
} else {
false
}
}
}
#[derive(Debug)]
pub struct QueryParamExactMatcher(String, String);
impl QueryParamExactMatcher {
pub fn new<K: Into<String>, V: Into<String>>(key: K, value: V) -> Self {
let key = key.into();
let value = value.into();
Self(key, value)
}
}
pub fn query_param<K, V>(key: K, value: V) -> QueryParamExactMatcher
where
K: Into<String>,
V: Into<String>,
{
QueryParamExactMatcher::new(key, value)
}
impl Match for QueryParamExactMatcher {
fn matches(&self, request: &Request) -> bool {
request
.url
.query_pairs()
.any(|q| q.0 == self.0.as_str() && q.1 == self.1.as_str())
}
}
#[derive(Debug)]
pub struct QueryParamIsMissingMatcher(String);
impl QueryParamIsMissingMatcher {
pub fn new<K: Into<String>>(key: K) -> Self {
let key = key.into();
Self(key)
}
}
pub fn query_param_is_missing<K>(key: K) -> QueryParamIsMissingMatcher
where
K: Into<String>,
{
QueryParamIsMissingMatcher::new(key)
}
impl Match for QueryParamIsMissingMatcher {
fn matches(&self, request: &Request) -> bool {
!request.url.query_pairs().any(|(k, _)| k == self.0)
}
}
pub fn body_json_schema<T>(request: &Request) -> bool
where
for<'de> T: serde::de::Deserialize<'de>,
{
serde_json::from_slice::<T>(&request.body).is_ok()
}
#[derive(Debug)]
pub struct BasicAuthMatcher(HeaderExactMatcher);
impl BasicAuthMatcher {
pub fn from_credentials(username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
Self::from_token(base64::encode(format!(
"{}:{}",
username.as_ref(),
password.as_ref()
)))
}
pub fn from_token(token: impl AsRef<str>) -> Self {
Self(header(
"Authorization",
format!("Basic {}", token.as_ref()).deref(),
))
}
}
pub fn basic_auth<U, P>(username: U, password: P) -> BasicAuthMatcher
where
U: AsRef<str>,
P: AsRef<str>,
{
BasicAuthMatcher::from_credentials(username, password)
}
impl Match for BasicAuthMatcher {
fn matches(&self, request: &Request) -> bool {
self.0.matches(request)
}
}
#[derive(Debug)]
pub struct BearerTokenMatcher(HeaderExactMatcher);
impl BearerTokenMatcher {
pub fn from_token(token: impl AsRef<str>) -> Self {
Self(header(
"Authorization",
format!("Bearer {}", token.as_ref()).deref(),
))
}
}
impl Match for BearerTokenMatcher {
fn matches(&self, request: &Request) -> bool {
self.0.matches(request)
}
}
pub fn bearer_token<T>(token: T) -> BearerTokenMatcher
where
T: AsRef<str>,
{
BearerTokenMatcher::from_token(token)
}