extern crate serde_regex;
use crate::{
common::{
data::Error::{
HeaderDeserializationError, RequestConversionError, StaticMockConversionError,
},
util::HttpMockBytes,
},
server::matchers::generic::MatchingStrategy,
};
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{
cmp::Ordering,
collections::HashMap,
convert::{TryFrom, TryInto},
fmt,
fmt::Debug,
str::FromStr,
sync::Arc,
};
use url::Url;
use crate::server::RequestMetadata;
#[cfg(feature = "cookies")]
use headers::{Cookie, HeaderMapExt};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Cannot deserialize header: {0}")]
HeaderDeserializationError(String),
#[error("Cookie parser error: {0}")]
CookieParserError(String),
#[error("cannot convert to/from static mock: {0}")]
StaticMockConversionError(String),
#[error("JSONConversionError: {0}")]
JSONConversionError(#[from] serde_json::Error),
#[error("Invalid request data: {0}")]
InvalidRequestData(String),
#[error("Cannot convert request to/from internal structure: {0}")]
RequestConversionError(String),
#[error("Response conversion error: {0}")]
ResponseConversionError(String),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HttpMockRequest {
scheme: String,
uri: String,
method: String,
headers: Vec<(String, String)>,
version: String,
body: HttpMockBytes,
}
impl HttpMockRequest {
pub(crate) fn new(
scheme: String,
uri: String,
method: String,
headers: Vec<(String, String)>,
version: String,
body: HttpMockBytes,
) -> Self {
Self {
scheme,
uri,
method,
headers,
version,
body,
}
}
pub fn uri(&self) -> http::Uri {
self.uri.parse().unwrap()
}
pub fn scheme(&self) -> String {
let uri = self.uri();
if let Some(scheme) = uri.scheme() {
return scheme.to_string();
}
self.scheme.clone()
}
pub fn uri_str(&self) -> &str {
self.uri.as_ref()
}
pub fn host(&self) -> Option<String> {
if let Some((_, host)) = self
.headers
.iter()
.find(|&&(ref k, _)| k.eq_ignore_ascii_case("host"))
{
return Some(host.split(':').next().unwrap().to_string());
}
let uri = self.uri();
if let Some(authority) = uri.authority() {
return Some(authority.as_str().split(':').next().unwrap().to_string());
}
None
}
pub fn port(&self) -> u16 {
if let Some((_, host)) = self
.headers
.iter()
.find(|&&(ref k, _)| k.eq_ignore_ascii_case("host"))
{
if let Some(port_str) = host.split(':').nth(1) {
if let Ok(port) = port_str.parse::<u16>() {
return port;
}
}
}
let uri = self.uri();
if let Some(authority) = uri.authority() {
if let Some(port_str) = authority.as_str().split(':').nth(1) {
if let Ok(port) = port_str.parse::<u16>() {
return port;
}
}
}
if self.scheme().eq("https") {
return 443;
}
return 80;
}
pub fn method(&self) -> http::Method {
http::Method::from_bytes(self.method.as_bytes()).unwrap()
}
pub fn method_str(&self) -> &str {
self.method.as_ref()
}
pub fn headers(&self) -> http::HeaderMap<http::HeaderValue> {
let mut header_map: http::HeaderMap<http::HeaderValue> = http::HeaderMap::new();
for (key, value) in &self.headers {
let header_name = http::HeaderName::from_bytes(key.as_bytes()).unwrap();
let header_value = http::HeaderValue::from_str(&value).unwrap();
header_map.append(header_name, header_value);
}
header_map
}
pub fn headers_vec(&self) -> &Vec<(String, String)> {
self.headers.as_ref()
}
pub fn query_params_map(&self) -> HashMap<String, String> {
self.query_params().into_iter().collect()
}
pub fn query_params(&self) -> Vec<(String, String)> {
let url = format!("http://dummy?{}", self.uri().query().unwrap_or(""));
let url = Url::parse(&url).unwrap();
url.query_pairs()
.map(|(k, v)| (k.into_owned(), v.into_owned()))
.collect()
}
pub fn query_param_length(&self) -> usize {
let url = format!("http://dummy?{}", self.uri().query().unwrap_or(""));
let url = Url::parse(&url).unwrap();
url.query_pairs().count()
}
pub fn body(&self) -> &HttpMockBytes {
&self.body
}
pub fn body_string(&self) -> String {
self.body.to_string()
}
pub fn body_ref<'a>(&'a self) -> &'a [u8] {
self.body.as_ref()
}
pub fn body_vec(&self) -> Vec<u8> {
self.body.to_vec()
}
pub fn body_bytes(&self) -> bytes::Bytes {
self.body.to_bytes()
}
pub fn version(&self) -> http::Version {
match self.version.as_ref() {
"HTTP/0.9" => http::Version::HTTP_09,
"HTTP/1.0" => http::Version::HTTP_10,
"HTTP/1.1" => http::Version::HTTP_11,
"HTTP/2.0" => http::Version::HTTP_2,
"HTTP/3.0" => http::Version::HTTP_3,
_ => panic!("unknown HTTP version: {:?}", self.version),
}
}
pub fn version_ref(&self) -> &str {
self.version.as_ref()
}
#[cfg(feature = "cookies")]
pub(crate) fn cookies(&self) -> Result<Vec<(String, String)>, Error> {
let mut result = Vec::new();
if let Some(cookie) = self.headers().typed_get::<Cookie>() {
for (key, value) in cookie.iter() {
result.push((key.to_string(), value.to_string()));
}
}
Ok(result)
}
pub fn to_http_request(&self) -> http::Request<Bytes> {
http::Request::<Bytes>::from(self)
}
}
fn http_headers_to_vec<T>(req: &http::Request<T>) -> Result<Vec<(String, String)>, Error> {
req.headers()
.iter()
.map(|(name, value)| {
let value_str = value
.to_str()
.map_err(|e| RequestConversionError(e.to_string()))?;
Ok((name.as_str().to_string(), value_str.to_string()))
})
.collect()
}
impl<B> TryFrom<&http::Request<B>> for HttpMockRequest
where
B: Clone + IntoMockBytes,
{
type Error = Error;
fn try_from(value: &http::Request<B>) -> Result<Self, Self::Error> {
let metadata = value
.extensions()
.get::<RequestMetadata>()
.unwrap_or_else(|| panic!("request metadata was not added to the request"));
let headers = http_headers_to_vec(&value)?;
let body_bytes = value.body().clone().into_httpmock_bytes()?;
let body = HttpMockBytes(body_bytes);
Ok(HttpMockRequest::new(
metadata.scheme.to_string(),
value.uri().to_string(),
value.method().to_string(),
headers,
format!("{:?}", value.version()),
body,
))
}
}
impl<B> From<http::Request<B>> for HttpMockRequest
where
B: Clone + IntoMockBytes,
{
fn from(req: http::Request<B>) -> Self {
<HttpMockRequest as TryFrom<&http::Request<B>>>::try_from(&req).expect(
"invalid http::Request for HttpMockRequest: missing metadata or invalid headers/body",
)
}
}
impl From<&HttpMockRequest> for http::Request<bytes::Bytes> {
fn from(req: &HttpMockRequest) -> Self {
let mut builder = http::Request::builder()
.method(req.method())
.uri(req.uri())
.version(req.version());
for (k, v) in req.headers() {
builder = builder.header(k.map_or(String::new(), |v| v.to_string()), v)
}
builder
.body(req.body().to_bytes())
.expect("failed to convert HttpMockRequest into http::Request<Bytes>")
}
}
impl From<&HttpMockRequest> for http::Request<String> {
fn from(req: &HttpMockRequest) -> Self {
let mut builder = http::Request::builder()
.method(req.method())
.uri(req.uri())
.version(req.version());
for (k, v) in req.headers() {
builder = builder.header(k.map_or(String::new(), |v| v.to_string()), v)
}
let body = String::from_utf8(req.body_vec()).expect("request body is not valid UTF-8");
builder
.body(body)
.expect("failed to convert HttpMockRequest into http::Request<String>")
}
}
impl From<&HttpMockRequest> for http::Request<()> {
fn from(req: &HttpMockRequest) -> Self {
let mut builder = http::Request::builder()
.method(req.method())
.uri(req.uri())
.version(req.version());
for (k, v) in req.headers() {
builder = builder.header(k.map_or(String::new(), |v| v.to_string()), v)
}
builder
.body(())
.expect("failed to convert HttpMockRequest into http::Request<()>")
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct HttpMockResponse {
pub status: Option<u16>,
pub headers: Option<Vec<(String, String)>>,
#[serde(default, with = "opt_vector_serde_base64")]
pub body: Option<HttpMockBytes>,
}
impl HttpMockResponse {
pub fn builder() -> HttpMockResponseBuilder {
HttpMockResponseBuilder::new()
}
}
impl TryFrom<HttpMockResponse> for http::Response<bytes::Bytes> {
type Error = Error;
fn try_from(res: HttpMockResponse) -> Result<Self, Self::Error> {
(&res).try_into() }
}
impl TryFrom<&HttpMockResponse> for http::Response<bytes::Bytes> {
type Error = Error;
fn try_from(res: &HttpMockResponse) -> Result<Self, Self::Error> {
let raw_status = res
.status
.ok_or_else(|| Error::ResponseConversionError("missing status".into()))?;
let status = http::StatusCode::from_u16(raw_status).map_err(|_| {
Error::ResponseConversionError(format!("invalid status: {}", raw_status))
})?;
let mut builder = http::Response::builder().status(status);
if let Some(headers) = &res.headers {
for (name, value) in headers {
let header_name =
http::header::HeaderName::try_from(name.clone()).map_err(|_| {
Error::ResponseConversionError(format!("invalid header name: {}", name))
})?;
let header_value =
http::header::HeaderValue::try_from(value.clone()).map_err(|_| {
Error::ResponseConversionError(format!(
"invalid header value for '{}': {}",
name, value
))
})?;
builder = builder.header(header_name, header_value);
}
}
let body = res
.body
.as_ref()
.map_or(bytes::Bytes::new(), |b| b.0.clone());
builder
.body(body)
.map_err(|e| Error::ResponseConversionError(format!("http build error: {}", e)))
}
}
pub trait IntoMockBytes {
fn into_httpmock_bytes(self) -> Result<bytes::Bytes, Error>;
}
impl IntoMockBytes for bytes::Bytes {
fn into_httpmock_bytes(self) -> Result<bytes::Bytes, Error> {
Ok(self)
}
}
impl IntoMockBytes for Vec<u8> {
fn into_httpmock_bytes(self) -> Result<bytes::Bytes, Error> {
Ok(bytes::Bytes::from(self))
}
}
impl IntoMockBytes for String {
fn into_httpmock_bytes(self) -> Result<bytes::Bytes, Error> {
Ok(bytes::Bytes::from(self.into_bytes()))
}
}
impl IntoMockBytes for &'static str {
fn into_httpmock_bytes(self) -> Result<bytes::Bytes, Error> {
Ok(bytes::Bytes::from(self))
}
}
impl IntoMockBytes for Box<[u8]> {
fn into_httpmock_bytes(self) -> Result<bytes::Bytes, Error> {
Ok(bytes::Bytes::from(self))
}
}
impl<'a> IntoMockBytes for std::borrow::Cow<'a, [u8]> {
fn into_httpmock_bytes(self) -> Result<bytes::Bytes, Error> {
Ok(match self {
std::borrow::Cow::Borrowed(b) => bytes::Bytes::copy_from_slice(b),
std::borrow::Cow::Owned(v) => bytes::Bytes::from(v),
})
}
}
impl<'a> IntoMockBytes for std::borrow::Cow<'a, str> {
fn into_httpmock_bytes(self) -> Result<bytes::Bytes, Error> {
Ok(match self {
std::borrow::Cow::Borrowed(s) => bytes::Bytes::copy_from_slice(s.as_bytes()),
std::borrow::Cow::Owned(s) => bytes::Bytes::from(s.into_bytes()),
})
}
}
impl IntoMockBytes for () {
fn into_httpmock_bytes(self) -> Result<bytes::Bytes, Error> {
Ok(bytes::Bytes::new())
}
}
impl<B> TryFrom<&http::Response<B>> for HttpMockResponse
where
B: Clone + IntoMockBytes, {
type Error = Error;
fn try_from(resp: &http::Response<B>) -> Result<Self, Self::Error> {
let mut headers = Vec::with_capacity(resp.headers().len());
for (name, value) in resp.headers() {
let name = name.as_str().to_string();
let val = value.to_str().map_err(|_| {
Error::ResponseConversionError(format!("non-utf8 header value for '{}'", name))
})?;
headers.push((name, val.to_string()));
}
let body_bytes = resp.body().clone().into_httpmock_bytes()?;
Ok(HttpMockResponse {
status: Some(resp.status().as_u16()),
headers: Some(headers),
body: Some(HttpMockBytes(body_bytes)),
})
}
}
impl<B> From<http::Response<B>> for HttpMockResponse
where
B: Clone + IntoMockBytes,
{
fn from(resp: http::Response<B>) -> Self {
<HttpMockResponse as TryFrom<&http::Response<B>>>::try_from(&resp)
.expect("invalid http::Response for HttpMockResponse")
}
}
#[derive(Default, Debug, Clone)]
pub struct HttpMockResponseBuilder {
status: Option<u16>,
headers: Vec<(String, String)>,
body: Option<HttpMockBytes>,
}
impl HttpMockResponseBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn status(mut self, status: u16) -> Self {
self.status = Some(status);
self
}
pub fn header<K, V>(mut self, key: K, val: V) -> Self
where
K: Into<String>,
V: Into<String>,
{
self.headers.push((key.into(), val.into()));
self
}
pub fn headers<I, K, V>(mut self, headers: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
self.headers = headers
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect();
self
}
pub fn body<B>(mut self, body: B) -> Self
where
B: Into<HttpMockBytes>,
{
self.body = Some(body.into());
self
}
pub fn no_body(mut self) -> Self {
self.body = None;
self
}
pub fn build(self) -> HttpMockResponse {
HttpMockResponse {
status: self.status,
headers: if self.headers.is_empty() {
None
} else {
Some(self.headers)
},
body: self.body,
}
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct MockServerHttpResponse {
pub status: Option<u16>,
pub headers: Option<Vec<(String, String)>>,
#[serde(default, with = "opt_vector_serde_base64")]
pub body: Option<HttpMockBytes>,
pub delay: Option<u64>,
#[serde(skip)]
pub respond_with:
Option<std::sync::Arc<dyn Fn(&HttpMockRequest) -> HttpMockResponse + Send + Sync>>,
}
impl MockServerHttpResponse {
pub(crate) fn new() -> Self {
Self {
status: None,
headers: None,
body: None,
delay: None,
respond_with: None,
}
}
}
impl Default for MockServerHttpResponse {
fn default() -> Self {
Self::new()
}
}
impl TryFrom<&http::Response<Bytes>> for MockServerHttpResponse {
type Error = Error;
fn try_from(value: &http::Response<Bytes>) -> Result<Self, Self::Error> {
let mut headers = Vec::with_capacity(value.headers().len());
for (key, value) in value.headers() {
let value = value
.to_str()
.map_err(|err| HeaderDeserializationError(err.to_string()))?;
headers.push((key.as_str().to_string(), value.to_string()))
}
Ok(Self {
status: Some(value.status().as_u16()),
headers: if !headers.is_empty() {
Some(headers)
} else {
None
},
body: if !value.body().is_empty() {
Some(HttpMockBytes::from(value.body().clone()))
} else {
None
},
delay: None,
respond_with: None,
})
}
}
mod opt_vector_serde_base64 {
use crate::common::util::HttpMockBytes;
use bytes::Bytes;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<T, S>(bytes: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
S: Serializer,
{
match bytes {
Some(ref value) => serializer.serialize_bytes(base64::encode(value).as_bytes()),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<HttpMockBytes>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Wrapper(#[serde(deserialize_with = "from_base64")] HttpMockBytes);
let v = Option::deserialize(deserializer)?;
Ok(v.map(|Wrapper(a)| a))
}
fn from_base64<'de, D>(deserializer: D) -> Result<HttpMockBytes, D::Error>
where
D: Deserializer<'de>,
{
let value = Vec::deserialize(deserializer)?;
let decoded = base64::decode(value).map_err(serde::de::Error::custom)?;
Ok(HttpMockBytes::from(Bytes::from(decoded)))
}
}
impl fmt::Debug for MockServerHttpResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MockServerHttpResponse")
.field("status", &self.status)
.field("headers", &self.headers)
.field(
"body",
&self
.body
.as_ref()
.map(|x| String::from_utf8_lossy(x.as_ref()).to_string()),
)
.field("delay", &self.delay)
.finish()
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct HttpMockRegex(#[serde(with = "serde_regex")] pub regex::Regex);
impl Ord for HttpMockRegex {
fn cmp(&self, other: &Self) -> Ordering {
self.0.as_str().cmp(other.0.as_str())
}
}
impl PartialOrd for HttpMockRegex {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for HttpMockRegex {
fn eq(&self, other: &Self) -> bool {
self.0.as_str() == other.0.as_str()
}
}
impl Eq for HttpMockRegex {}
impl From<regex::Regex> for HttpMockRegex {
fn from(value: regex::Regex) -> Self {
HttpMockRegex(value)
}
}
impl From<&str> for HttpMockRegex {
fn from(value: &str) -> Self {
let re = regex::Regex::from_str(value).expect("cannot parse value as regex");
HttpMockRegex::from(re)
}
}
impl From<String> for HttpMockRegex {
fn from(value: String) -> Self {
HttpMockRegex::from(value.as_str())
}
}
impl fmt::Display for HttpMockRegex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct RequestRequirements {
pub scheme: Option<String>,
pub scheme_not: Option<String>, pub host: Option<String>,
pub host_not: Option<Vec<String>>, pub host_contains: Option<Vec<String>>, pub host_excludes: Option<Vec<String>>, pub host_prefix: Option<Vec<String>>, pub host_suffix: Option<Vec<String>>, pub host_prefix_not: Option<Vec<String>>, pub host_suffix_not: Option<Vec<String>>, pub host_matches: Option<Vec<HttpMockRegex>>,
pub port: Option<u16>,
pub port_not: Option<Vec<u16>>, pub method: Option<String>,
pub method_not: Option<Vec<String>>, pub path: Option<String>,
pub path_not: Option<Vec<String>>, pub path_includes: Option<Vec<String>>, pub path_excludes: Option<Vec<String>>, pub path_prefix: Option<Vec<String>>, pub path_suffix: Option<Vec<String>>, pub path_prefix_not: Option<Vec<String>>, pub path_suffix_not: Option<Vec<String>>, pub path_matches: Option<Vec<HttpMockRegex>>,
pub query_param: Option<Vec<(String, String)>>,
pub query_param_not: Option<Vec<(String, String)>>, pub query_param_exists: Option<Vec<String>>,
pub query_param_missing: Option<Vec<String>>, pub query_param_includes: Option<Vec<(String, String)>>, pub query_param_excludes: Option<Vec<(String, String)>>, pub query_param_prefix: Option<Vec<(String, String)>>, pub query_param_suffix: Option<Vec<(String, String)>>, pub query_param_prefix_not: Option<Vec<(String, String)>>, pub query_param_suffix_not: Option<Vec<(String, String)>>, pub query_param_matches: Option<Vec<(HttpMockRegex, HttpMockRegex)>>, pub query_param_count: Option<Vec<(HttpMockRegex, HttpMockRegex, usize)>>, pub header: Option<Vec<(String, String)>>, pub header_not: Option<Vec<(String, String)>>, pub header_exists: Option<Vec<String>>,
pub header_missing: Option<Vec<String>>, pub header_includes: Option<Vec<(String, String)>>, pub header_excludes: Option<Vec<(String, String)>>, pub header_prefix: Option<Vec<(String, String)>>, pub header_suffix: Option<Vec<(String, String)>>, pub header_prefix_not: Option<Vec<(String, String)>>, pub header_suffix_not: Option<Vec<(String, String)>>, pub header_matches: Option<Vec<(HttpMockRegex, HttpMockRegex)>>, pub header_count: Option<Vec<(HttpMockRegex, HttpMockRegex, usize)>>, pub cookie: Option<Vec<(String, String)>>, pub cookie_not: Option<Vec<(String, String)>>, pub cookie_exists: Option<Vec<String>>,
pub cookie_missing: Option<Vec<String>>, pub cookie_includes: Option<Vec<(String, String)>>, pub cookie_excludes: Option<Vec<(String, String)>>, pub cookie_prefix: Option<Vec<(String, String)>>, pub cookie_suffix: Option<Vec<(String, String)>>, pub cookie_prefix_not: Option<Vec<(String, String)>>, pub cookie_suffix_not: Option<Vec<(String, String)>>, pub cookie_matches: Option<Vec<(HttpMockRegex, HttpMockRegex)>>, pub cookie_count: Option<Vec<(HttpMockRegex, HttpMockRegex, usize)>>, pub body: Option<HttpMockBytes>,
pub body_not: Option<Vec<HttpMockBytes>>, pub body_includes: Option<Vec<HttpMockBytes>>, pub body_excludes: Option<Vec<HttpMockBytes>>, pub body_prefix: Option<Vec<HttpMockBytes>>, pub body_suffix: Option<Vec<HttpMockBytes>>, pub body_prefix_not: Option<Vec<HttpMockBytes>>, pub body_suffix_not: Option<Vec<HttpMockBytes>>, pub body_matches: Option<Vec<HttpMockRegex>>, pub json_body: Option<Value>,
pub json_body_not: Option<Value>, pub json_body_includes: Option<Vec<Value>>,
pub json_body_excludes: Option<Vec<Value>>, pub form_urlencoded_tuple: Option<Vec<(String, String)>>,
pub form_urlencoded_tuple_not: Option<Vec<(String, String)>>, pub form_urlencoded_tuple_exists: Option<Vec<String>>,
pub form_urlencoded_tuple_missing: Option<Vec<String>>, pub form_urlencoded_tuple_includes: Option<Vec<(String, String)>>, pub form_urlencoded_tuple_excludes: Option<Vec<(String, String)>>, pub form_urlencoded_tuple_prefix: Option<Vec<(String, String)>>, pub form_urlencoded_tuple_suffix: Option<Vec<(String, String)>>, pub form_urlencoded_tuple_prefix_not: Option<Vec<(String, String)>>, pub form_urlencoded_tuple_suffix_not: Option<Vec<(String, String)>>, pub form_urlencoded_tuple_matches: Option<Vec<(HttpMockRegex, HttpMockRegex)>>, pub form_urlencoded_tuple_count: Option<Vec<(HttpMockRegex, HttpMockRegex, usize)>>, #[serde(skip)]
pub is_true: Option<Vec<Arc<dyn Fn(&HttpMockRequest) -> bool + Sync + Send>>>, #[serde(skip)]
pub is_false: Option<Vec<Arc<dyn Fn(&HttpMockRequest) -> bool + Sync + Send>>>, }
impl Default for RequestRequirements {
fn default() -> Self {
Self::new()
}
}
impl RequestRequirements {
pub fn new() -> Self {
Self {
scheme: None,
scheme_not: None,
host: None,
host_not: None,
host_contains: None,
host_excludes: None,
host_prefix: None,
host_suffix: None,
host_prefix_not: None,
host_suffix_not: None,
host_matches: None,
port: None,
path: None,
path_not: None,
path_includes: None,
path_excludes: None,
path_prefix: None,
path_suffix: None,
path_prefix_not: None,
path_suffix_not: None,
path_matches: None,
method: None,
header: None,
header_not: None,
header_exists: None,
header_missing: None,
header_includes: None,
header_excludes: None,
header_prefix: None,
header_suffix: None,
header_prefix_not: None,
header_suffix_not: None,
header_matches: None,
header_count: None,
cookie: None,
cookie_not: None,
cookie_exists: None,
cookie_missing: None,
cookie_includes: None,
cookie_excludes: None,
cookie_prefix: None,
cookie_suffix: None,
cookie_prefix_not: None,
cookie_suffix_not: None,
cookie_matches: None,
cookie_count: None,
body: None,
json_body: None,
json_body_not: None,
json_body_includes: None,
body_includes: None,
body_excludes: None,
body_prefix: None,
body_suffix: None,
body_prefix_not: None,
body_suffix_not: None,
body_matches: None,
query_param_exists: None,
query_param_missing: None,
query_param_includes: None,
query_param_excludes: None,
query_param_prefix: None,
query_param_suffix: None,
query_param_prefix_not: None,
query_param_suffix_not: None,
query_param_matches: None,
query_param_count: None,
query_param: None,
form_urlencoded_tuple: None,
form_urlencoded_tuple_not: None,
form_urlencoded_tuple_exists: None,
form_urlencoded_tuple_missing: None,
form_urlencoded_tuple_includes: None,
form_urlencoded_tuple_excludes: None,
form_urlencoded_tuple_prefix: None,
form_urlencoded_tuple_suffix: None,
form_urlencoded_tuple_prefix_not: None,
form_urlencoded_tuple_suffix_not: None,
form_urlencoded_tuple_matches: None,
form_urlencoded_tuple_count: None,
is_true: None,
port_not: None,
method_not: None,
query_param_not: None,
body_not: None,
json_body_excludes: None,
is_false: None,
}
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct MockDefinition {
pub request: RequestRequirements,
pub response: MockServerHttpResponse,
}
impl MockDefinition {
pub fn new(req: RequestRequirements, mock: MockServerHttpResponse) -> Self {
Self {
request: req,
response: mock,
}
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ActiveMock {
pub id: usize,
pub call_counter: usize,
pub definition: MockDefinition,
pub is_static: bool,
}
impl ActiveMock {
pub fn new(
id: usize,
definition: MockDefinition,
call_counter: usize,
is_static: bool,
) -> Self {
ActiveMock {
id,
definition,
call_counter,
is_static,
}
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ActiveForwardingRule {
pub id: usize,
pub config: ForwardingRuleConfig,
}
impl ActiveForwardingRule {
pub fn new(id: usize, config: ForwardingRuleConfig) -> Self {
ActiveForwardingRule { id, config }
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ActiveProxyRule {
pub id: usize,
pub config: ProxyRuleConfig,
}
impl ActiveProxyRule {
pub fn new(id: usize, config: ProxyRuleConfig) -> Self {
ActiveProxyRule { id, config }
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ActiveRecording {
pub id: usize,
pub config: RecordingRuleConfig,
pub mocks: Vec<MockDefinition>,
}
impl ActiveRecording {
pub fn new(id: usize, config: RecordingRuleConfig) -> Self {
ActiveRecording {
id,
config,
mocks: vec![],
}
}
}
#[derive(Serialize, Deserialize)]
pub struct ClosestMatch {
pub request: HttpMockRequest,
pub request_index: usize,
pub mismatches: Vec<Mismatch>,
}
#[derive(Serialize, Deserialize)]
pub struct ErrorResponse {
pub message: String,
}
impl ErrorResponse {
pub fn new<T>(message: &T) -> ErrorResponse
where
T: ToString,
{
ErrorResponse {
message: message.to_string(),
}
}
}
#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub enum Diff {
Same(String),
Add(String),
Rem(String),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DiffResult {
pub differences: Vec<Diff>,
pub distance: f32,
pub tokenizer: Tokenizer,
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Copy)]
pub enum Tokenizer {
Line,
Word,
Character,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyValueComparisonKeyValuePair {
pub key: String,
pub value: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyValueComparisonAttribute {
pub operator: String,
pub expected: String,
pub actual: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyValueComparison {
pub key: Option<KeyValueComparisonAttribute>,
pub value: Option<KeyValueComparisonAttribute>,
pub expected_count: Option<usize>,
pub actual_count: Option<usize>,
pub all: Vec<KeyValueComparisonKeyValuePair>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FunctionComparison {
pub index: usize,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SingleValueComparison {
pub operator: String,
pub expected: String,
pub actual: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Mismatch {
pub entity: String,
pub matcher_method: String,
pub comparison: Option<SingleValueComparison>,
pub key_value_comparison: Option<KeyValueComparison>,
pub function_comparison: Option<FunctionComparison>,
pub matching_strategy: Option<MatchingStrategy>,
pub best_match: bool,
pub diff: Option<DiffResult>,
}
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct RecordingRuleConfig {
pub request_requirements: RequestRequirements,
pub record_headers: Vec<String>,
pub record_response_delays: bool,
}
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct ProxyRuleConfig {
pub request_requirements: RequestRequirements,
pub request_header: Vec<(String, String)>,
}
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct ForwardingRuleConfig {
pub target_base_url: String,
pub request_requirements: RequestRequirements,
pub request_header: Vec<(String, String)>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct NameValueStringPair {
name: String,
value: String,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct NameValuePatternPair {
name: HttpMockRegex,
value: HttpMockRegex,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct KeyValuePatternCountTriple {
name: HttpMockRegex,
value: HttpMockRegex,
count: usize,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StaticRequestRequirements {
#[serde(skip_serializing_if = "Option::is_none")]
pub scheme: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scheme_not: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_not: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_contains: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_excludes: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_prefix: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_suffix: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_prefix_not: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_suffix_not: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_matches: Option<Vec<HttpMockRegex>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port_not: Option<Vec<u16>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_not: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_contains: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_excludes: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_prefix: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_suffix: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_prefix_not: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_suffix_not: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_matches: Option<Vec<HttpMockRegex>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<Method>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method_not: Option<Vec<Method>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_exists: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_missing: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_contains: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_excludes: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_prefix: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_suffix: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_prefix_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_suffix_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_matches: Option<Vec<NameValuePatternPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param_count: Option<Vec<KeyValuePatternCountTriple>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_exists: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_missing: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_contains: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_excludes: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_prefix: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_suffix: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_prefix_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_suffix_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_matches: Option<Vec<NameValuePatternPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_count: Option<Vec<KeyValuePatternCountTriple>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_exists: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_missing: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_contains: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_excludes: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_prefix: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_suffix: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_prefix_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_suffix_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_matches: Option<Vec<NameValuePatternPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_count: Option<Vec<KeyValuePatternCountTriple>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_base64: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_not: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_not_base64: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_contains: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_contains_base64: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_excludes: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_excludes_base64: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_prefix: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_prefix_base64: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_suffix: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_suffix_base64: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_prefix_not: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_prefix_not_base64: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_suffix_not: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_suffix_not_base64: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_matches: Option<Vec<HttpMockRegex>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub json_body: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub json_body_not: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub json_body_includes: Option<Vec<Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub json_body_excludes: Option<Vec<Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_tuple: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_tuple_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_key_exists: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_key_missing: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_contains: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_excludes: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_prefix: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_suffix: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_prefix_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_suffix_not: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_matches: Option<Vec<NameValuePatternPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub form_urlencoded_count: Option<Vec<KeyValuePatternCountTriple>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StaticHTTPResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header: Option<Vec<NameValueStringPair>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_base64: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StaticMockDefinition {
when: StaticRequestRequirements,
then: StaticHTTPResponse,
}
impl TryInto<MockDefinition> for StaticMockDefinition {
type Error = Error;
fn try_into(self) -> Result<MockDefinition, Self::Error> {
Ok(MockDefinition {
request: RequestRequirements {
scheme: self.when.scheme,
scheme_not: self.when.scheme_not,
host: self.when.host,
host_not: self.when.host_not,
host_contains: self.when.host_contains,
host_excludes: self.when.host_excludes,
host_prefix: self.when.host_prefix,
host_suffix: self.when.host_suffix,
host_prefix_not: self.when.host_prefix_not,
host_suffix_not: self.when.host_suffix_not,
host_matches: self.when.host_matches,
port: self.when.port,
port_not: self.when.port_not,
path: self.when.path,
path_not: self.when.path_not,
path_includes: self.when.path_contains,
path_excludes: self.when.path_excludes,
path_prefix: self.when.path_prefix,
path_suffix: self.when.path_suffix,
path_prefix_not: self.when.path_prefix_not,
path_suffix_not: self.when.path_suffix_not,
path_matches: self.when.path_matches,
method: self.when.method.map(|m| m.to_string()),
method_not: from_method_vec(self.when.method_not),
query_param: from_name_value_string_pair_vec(self.when.query_param),
query_param_not: from_name_value_string_pair_vec(self.when.query_param_not),
query_param_exists: self.when.query_param_exists,
query_param_missing: self.when.query_param_missing,
query_param_includes: from_name_value_string_pair_vec(
self.when.query_param_contains,
),
query_param_excludes: from_name_value_string_pair_vec(
self.when.query_param_excludes,
),
query_param_prefix: from_name_value_string_pair_vec(self.when.query_param_prefix),
query_param_suffix: from_name_value_string_pair_vec(self.when.query_param_suffix),
query_param_prefix_not: from_name_value_string_pair_vec(
self.when.query_param_prefix_not,
),
query_param_suffix_not: from_name_value_string_pair_vec(
self.when.query_param_suffix_not,
),
query_param_matches: from_name_value_pattern_pair_vec(
self.when.query_param_matches,
),
query_param_count: from_key_value_pattern_count_triple_vec(
self.when.query_param_count,
),
header: from_name_value_string_pair_vec(self.when.header),
header_not: from_name_value_string_pair_vec(self.when.header_not),
header_exists: self.when.header_exists,
header_missing: self.when.header_missing,
header_includes: from_name_value_string_pair_vec(self.when.header_contains),
header_excludes: from_name_value_string_pair_vec(self.when.header_excludes),
header_prefix: from_name_value_string_pair_vec(self.when.header_prefix),
header_suffix: from_name_value_string_pair_vec(self.when.header_suffix),
header_prefix_not: from_name_value_string_pair_vec(self.when.header_prefix_not),
header_suffix_not: from_name_value_string_pair_vec(self.when.header_suffix_not),
header_matches: from_name_value_pattern_pair_vec(self.when.header_matches),
header_count: from_key_value_pattern_count_triple_vec(self.when.header_count),
cookie: from_name_value_string_pair_vec(self.when.cookie),
cookie_not: from_name_value_string_pair_vec(self.when.cookie_not),
cookie_exists: self.when.cookie_exists,
cookie_missing: self.when.cookie_missing,
cookie_includes: from_name_value_string_pair_vec(self.when.cookie_contains),
cookie_excludes: from_name_value_string_pair_vec(self.when.cookie_excludes),
cookie_prefix: from_name_value_string_pair_vec(self.when.cookie_prefix),
cookie_suffix: from_name_value_string_pair_vec(self.when.cookie_suffix),
cookie_prefix_not: from_name_value_string_pair_vec(self.when.cookie_prefix_not),
cookie_suffix_not: from_name_value_string_pair_vec(self.when.cookie_suffix_not),
cookie_matches: from_name_value_pattern_pair_vec(self.when.cookie_matches),
cookie_count: from_key_value_pattern_count_triple_vec(self.when.cookie_count),
body: from_string_to_bytes_choose(self.when.body, self.when.body_base64),
body_not: to_bytes_vec(self.when.body_not, self.when.body_not_base64),
body_includes: to_bytes_vec(
self.when.body_contains,
self.when.body_contains_base64,
),
body_excludes: to_bytes_vec(
self.when.body_excludes,
self.when.body_excludes_base64,
),
body_prefix: to_bytes_vec(self.when.body_prefix, self.when.body_prefix_base64),
body_suffix: to_bytes_vec(self.when.body_suffix, self.when.body_suffix_base64),
body_prefix_not: to_bytes_vec(
self.when.body_prefix_not,
self.when.body_prefix_not_base64,
),
body_suffix_not: to_bytes_vec(
self.when.body_suffix_not,
self.when.body_suffix_not_base64,
),
body_matches: from_pattern_vec(self.when.body_matches),
json_body: self.when.json_body,
json_body_not: self.when.json_body_not,
json_body_includes: self.when.json_body_includes,
json_body_excludes: self.when.json_body_excludes,
form_urlencoded_tuple: from_name_value_string_pair_vec(
self.when.form_urlencoded_tuple,
),
form_urlencoded_tuple_not: from_name_value_string_pair_vec(
self.when.form_urlencoded_tuple_not,
),
form_urlencoded_tuple_exists: self.when.form_urlencoded_key_exists,
form_urlencoded_tuple_missing: self.when.form_urlencoded_key_missing,
form_urlencoded_tuple_includes: from_name_value_string_pair_vec(
self.when.form_urlencoded_contains,
),
form_urlencoded_tuple_excludes: from_name_value_string_pair_vec(
self.when.form_urlencoded_excludes,
),
form_urlencoded_tuple_prefix: from_name_value_string_pair_vec(
self.when.form_urlencoded_prefix,
),
form_urlencoded_tuple_suffix: from_name_value_string_pair_vec(
self.when.form_urlencoded_suffix,
),
form_urlencoded_tuple_prefix_not: from_name_value_string_pair_vec(
self.when.form_urlencoded_prefix_not,
),
form_urlencoded_tuple_suffix_not: from_name_value_string_pair_vec(
self.when.form_urlencoded_suffix_not,
),
form_urlencoded_tuple_matches: from_name_value_pattern_pair_vec(
self.when.form_urlencoded_matches,
),
form_urlencoded_tuple_count: from_key_value_pattern_count_triple_vec(
self.when.form_urlencoded_count,
),
is_true: None,
is_false: None,
},
response: MockServerHttpResponse {
status: self.then.status,
headers: from_name_value_string_pair_vec(self.then.header),
body: from_string_to_bytes_choose(self.then.body, self.then.body_base64),
delay: self.then.delay,
respond_with: None,
},
})
}
}
fn to_method_vec(vec: Option<Vec<String>>) -> Option<Vec<Method>> {
vec.map(|vec| vec.iter().map(|val| Method::from(val.as_str())).collect())
}
fn from_method_vec(value: Option<Vec<Method>>) -> Option<Vec<String>> {
value.map(|vec| vec.iter().map(|m| m.to_string()).collect())
}
fn from_pattern_vec(patterns: Option<Vec<HttpMockRegex>>) -> Option<Vec<HttpMockRegex>> {
patterns.map(|vec| vec.iter().cloned().collect())
}
fn from_name_value_string_pair_vec(
kvp: Option<Vec<NameValueStringPair>>,
) -> Option<Vec<(String, String)>> {
kvp.map(|vec| vec.into_iter().map(|nvp| (nvp.name, nvp.value)).collect())
}
fn from_name_value_pattern_pair_vec(
kvp: Option<Vec<NameValuePatternPair>>,
) -> Option<Vec<(HttpMockRegex, HttpMockRegex)>> {
kvp.map(|vec| {
vec.into_iter()
.map(|pair| (pair.name, pair.value))
.collect()
})
}
fn from_string_pair_vec(vec: Option<Vec<(String, String)>>) -> Option<Vec<NameValueStringPair>> {
vec.map(|vec| {
vec.into_iter()
.map(|(name, value)| NameValueStringPair { name, value })
.collect()
})
}
fn from_key_value_pattern_count_triple_vec(
input: Option<Vec<KeyValuePatternCountTriple>>,
) -> Option<Vec<(HttpMockRegex, HttpMockRegex, usize)>> {
input.map(|vec| {
vec.into_iter()
.map(|triple| (triple.name, triple.value, triple.count))
.collect()
})
}
fn to_name_value_string_pair_vec(
vec: Option<Vec<(String, String)>>,
) -> Option<Vec<NameValueStringPair>> {
vec.map(|vec| {
vec.into_iter()
.map(|(name, value)| NameValueStringPair { name, value })
.collect()
})
}
fn to_name_value_pattern_pair_vec(
vec: Option<Vec<(HttpMockRegex, HttpMockRegex)>>,
) -> Option<Vec<NameValuePatternPair>> {
vec.map(|vec| {
vec.into_iter()
.map(|(name, value)| NameValuePatternPair { name, value })
.collect()
})
}
fn to_key_value_pattern_count_triple_vec(
vec: Option<Vec<(HttpMockRegex, HttpMockRegex, usize)>>,
) -> Option<Vec<KeyValuePatternCountTriple>> {
vec.map(|vec| {
vec.into_iter()
.map(|(name, value, count)| KeyValuePatternCountTriple { name, value, count })
.collect()
})
}
fn from_bytes_to_string(data: Option<HttpMockBytes>) -> (Option<String>, Option<String>) {
let mut text_representation = None;
let mut base64_representation = None;
if let Some(bytes_container) = data {
if let Ok(text_str) = std::str::from_utf8(&bytes_container.to_bytes()) {
text_representation = Some(text_str.to_string());
} else {
base64_representation = Some(base64::encode(&bytes_container.to_bytes()));
}
}
(text_representation, base64_representation)
}
fn bytes_to_string_vec(
data: Option<Vec<HttpMockBytes>>,
) -> (Option<Vec<String>>, Option<Vec<String>>) {
let mut text_representations = Vec::new();
let mut base64_representations = Vec::new();
if let Some(bytes_vec) = data {
for bytes_container in bytes_vec {
let bytes = bytes_container.to_bytes();
if let Ok(text) = std::str::from_utf8(&bytes) {
text_representations.push(text.to_owned());
} else {
base64_representations.push(base64::encode(&bytes));
}
}
}
let text_opt_vec = if !text_representations.is_empty() {
Some(text_representations)
} else {
None
};
let base64_opt_vec = if !base64_representations.is_empty() {
Some(base64_representations)
} else {
None
};
(text_opt_vec, base64_opt_vec)
}
fn to_bytes_vec(
option_string: Option<Vec<String>>,
option_base64: Option<Vec<String>>,
) -> Option<Vec<HttpMockBytes>> {
let mut result = Vec::new();
if let Some(strings) = option_string {
result.extend(
strings
.into_iter()
.map(|s| HttpMockBytes::from(Bytes::from(s))),
);
}
if let Some(base64_strings) = option_base64 {
result.extend(base64_strings.into_iter().filter_map(|s| {
base64::decode(&s)
.ok()
.map(|decoded_bytes| HttpMockBytes::from(Bytes::from(decoded_bytes)))
}));
}
if result.is_empty() {
None
} else {
Some(result)
}
}
fn from_string_to_bytes_choose(
option_string: Option<String>,
option_base64: Option<String>,
) -> Option<HttpMockBytes> {
let request_body = match (option_string, option_base64) {
(Some(body), None) => Some(body.into_bytes()),
(None, Some(base64_body)) => base64::decode(base64_body).ok(),
_ => None, };
return request_body.map(|s| HttpMockBytes::from(Bytes::from(s)));
}
impl TryFrom<&MockDefinition> for StaticMockDefinition {
type Error = Error;
fn try_from(value: &MockDefinition) -> Result<Self, Self::Error> {
let value = value.clone();
let (response_body, response_body_base64) = from_bytes_to_string(value.response.body);
let (request_body, request_body_base64) = from_bytes_to_string(value.request.body);
let (request_body_not, request_body_not_base64) =
bytes_to_string_vec(value.request.body_not);
let (request_body_includes, request_body_includes_base64) =
bytes_to_string_vec(value.request.body_includes);
let (request_body_excludes, request_body_excludes_base64) =
bytes_to_string_vec(value.request.body_excludes);
let (request_body_prefix, request_body_prefix_base64) =
bytes_to_string_vec(value.request.body_prefix);
let (request_body_suffix, request_body_suffix_base64) =
bytes_to_string_vec(value.request.body_suffix);
let (request_body_prefix_not, request_body_prefix_not_base64) =
bytes_to_string_vec(value.request.body_prefix_not);
let (request_body_suffix_not, request_body_suffix_not_base64) =
bytes_to_string_vec(value.request.body_suffix_not);
let mut method = None;
if let Some(method_str) = value.request.method {
method = Some(
Method::from_str(&method_str)
.map_err(|err| StaticMockConversionError(err.to_string()))?,
);
}
Ok(StaticMockDefinition {
when: StaticRequestRequirements {
scheme: value.request.scheme,
scheme_not: value.request.scheme_not,
method,
method_not: to_method_vec(value.request.method_not),
host: value.request.host,
host_not: value.request.host_not,
host_contains: value.request.host_contains,
host_excludes: value.request.host_excludes,
host_prefix: value.request.host_prefix,
host_suffix: value.request.host_suffix,
host_prefix_not: value.request.host_prefix_not,
host_suffix_not: value.request.host_suffix_not,
host_matches: value.request.host_matches,
port: value.request.port,
port_not: value.request.port_not,
path: value.request.path,
path_not: value.request.path_not,
path_contains: value.request.path_includes,
path_excludes: value.request.path_excludes,
path_prefix: value.request.path_prefix,
path_suffix: value.request.path_suffix,
path_prefix_not: value.request.path_prefix_not,
path_suffix_not: value.request.path_suffix_not,
path_matches: from_pattern_vec(value.request.path_matches),
header: from_string_pair_vec(value.request.header),
header_not: from_string_pair_vec(value.request.header_not),
header_exists: value.request.header_exists,
header_missing: value.request.header_missing,
header_contains: to_name_value_string_pair_vec(value.request.header_includes),
header_excludes: to_name_value_string_pair_vec(value.request.header_excludes),
header_prefix: to_name_value_string_pair_vec(value.request.header_prefix),
header_suffix: to_name_value_string_pair_vec(value.request.header_suffix),
header_prefix_not: to_name_value_string_pair_vec(value.request.header_prefix_not),
header_suffix_not: to_name_value_string_pair_vec(value.request.header_suffix_not),
header_matches: to_name_value_pattern_pair_vec(value.request.header_matches),
header_count: to_key_value_pattern_count_triple_vec(value.request.header_count),
cookie: from_string_pair_vec(value.request.cookie),
cookie_not: from_string_pair_vec(value.request.cookie_not),
cookie_exists: value.request.cookie_exists,
cookie_missing: value.request.cookie_missing,
cookie_contains: to_name_value_string_pair_vec(value.request.cookie_includes),
cookie_excludes: to_name_value_string_pair_vec(value.request.cookie_excludes),
cookie_prefix: to_name_value_string_pair_vec(value.request.cookie_prefix),
cookie_suffix: to_name_value_string_pair_vec(value.request.cookie_suffix),
cookie_prefix_not: to_name_value_string_pair_vec(value.request.cookie_prefix_not),
cookie_suffix_not: to_name_value_string_pair_vec(value.request.cookie_suffix_not),
cookie_matches: to_name_value_pattern_pair_vec(value.request.cookie_matches),
cookie_count: to_key_value_pattern_count_triple_vec(value.request.cookie_count),
query_param: from_string_pair_vec(value.request.query_param),
query_param_not: from_string_pair_vec(value.request.query_param_not),
query_param_exists: value.request.query_param_exists,
query_param_missing: value.request.query_param_missing,
query_param_contains: to_name_value_string_pair_vec(
value.request.query_param_includes,
),
query_param_excludes: to_name_value_string_pair_vec(
value.request.query_param_excludes,
),
query_param_prefix: to_name_value_string_pair_vec(value.request.query_param_prefix),
query_param_suffix: to_name_value_string_pair_vec(value.request.query_param_suffix),
query_param_prefix_not: to_name_value_string_pair_vec(
value.request.query_param_prefix_not,
),
query_param_suffix_not: to_name_value_string_pair_vec(
value.request.query_param_suffix_not,
),
query_param_matches: to_name_value_pattern_pair_vec(
value.request.query_param_matches,
),
query_param_count: to_key_value_pattern_count_triple_vec(
value.request.query_param_count,
),
body: request_body,
body_base64: request_body_base64,
body_not: request_body_not,
body_not_base64: request_body_not_base64,
body_contains: request_body_includes,
body_contains_base64: request_body_includes_base64,
body_excludes: request_body_excludes,
body_excludes_base64: request_body_excludes_base64,
body_prefix: request_body_prefix,
body_prefix_base64: request_body_prefix_base64,
body_suffix: request_body_suffix,
body_suffix_base64: request_body_suffix_base64,
body_prefix_not: request_body_prefix_not,
body_prefix_not_base64: request_body_prefix_not_base64,
body_suffix_not: request_body_suffix_not,
body_suffix_not_base64: request_body_suffix_not_base64,
body_matches: from_pattern_vec(value.request.body_matches),
json_body: value.request.json_body,
json_body_not: value.request.json_body_not,
json_body_includes: value.request.json_body_includes,
json_body_excludes: value.request.json_body_excludes,
form_urlencoded_tuple: from_string_pair_vec(value.request.form_urlencoded_tuple),
form_urlencoded_tuple_not: from_string_pair_vec(
value.request.form_urlencoded_tuple_not,
),
form_urlencoded_key_exists: value.request.form_urlencoded_tuple_exists,
form_urlencoded_key_missing: value.request.form_urlencoded_tuple_missing,
form_urlencoded_contains: to_name_value_string_pair_vec(
value.request.form_urlencoded_tuple_includes,
),
form_urlencoded_excludes: to_name_value_string_pair_vec(
value.request.form_urlencoded_tuple_excludes,
),
form_urlencoded_prefix: to_name_value_string_pair_vec(
value.request.form_urlencoded_tuple_prefix,
),
form_urlencoded_suffix: to_name_value_string_pair_vec(
value.request.form_urlencoded_tuple_suffix,
),
form_urlencoded_prefix_not: to_name_value_string_pair_vec(
value.request.form_urlencoded_tuple_prefix_not,
),
form_urlencoded_suffix_not: to_name_value_string_pair_vec(
value.request.form_urlencoded_tuple_suffix_not,
),
form_urlencoded_matches: to_name_value_pattern_pair_vec(
value.request.form_urlencoded_tuple_matches,
),
form_urlencoded_count: to_key_value_pattern_count_triple_vec(
value.request.form_urlencoded_tuple_count,
),
},
then: StaticHTTPResponse {
status: value.response.status,
header: from_string_pair_vec(value.response.headers),
body: response_body,
body_base64: response_body_base64,
delay: value.response.delay,
},
})
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum Method {
GET,
HEAD,
POST,
PUT,
DELETE,
CONNECT,
OPTIONS,
TRACE,
PATCH,
}
impl PartialEq<Method> for http::method::Method {
fn eq(&self, other: &Method) -> bool {
self.to_string().to_uppercase() == other.to_string().to_uppercase()
}
}
impl FromStr for Method {
type Err = String;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match input.to_uppercase().as_str() {
"GET" => Ok(Method::GET),
"HEAD" => Ok(Method::HEAD),
"POST" => Ok(Method::POST),
"PUT" => Ok(Method::PUT),
"DELETE" => Ok(Method::DELETE),
"CONNECT" => Ok(Method::CONNECT),
"OPTIONS" => Ok(Method::OPTIONS),
"TRACE" => Ok(Method::TRACE),
"PATCH" => Ok(Method::PATCH),
_ => Err(format!("Invalid HTTP method {}", input)),
}
}
}
impl From<&str> for Method {
fn from(value: &str) -> Self {
value
.parse()
.expect(&format!("Cannot parse HTTP method from string {:?}", value))
}
}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}