use std::{cmp::Ordering, fmt, fmt::Debug, num::NonZero, str::FromStr, sync::Arc};
use bytes::Bytes;
#[cfg(feature = "cookies")]
use headers::{Cookie, HeaderMapExt};
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub type ResponseCallback = Arc<dyn Fn(&HttpMockRequest) -> HttpMockResponse + Send + Sync>;
pub type RequestPredicate = Arc<dyn Fn(&HttpMockRequest) -> bool + Send + Sync>;
use crate::{
common::util::HttpMockBytes,
server::{RequestMetadata, matchers::generic::MatchingStrategy},
};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Cannot deserialize header: {0}")]
HeaderDeserialization(String),
#[error("cannot convert to/from static mock: {0}")]
StaticMockConversion(String),
#[error("Cannot convert request to/from internal structure: {0}")]
RequestConversion(String),
#[error("Response conversion error: {0}")]
ResponseConversion(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(|(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(|(k, _)| k.eq_ignore_ascii_case("host"))
&& let Some(port_str) = host.split(':').nth(1)
&& let Ok(port) = port_str.parse::<u16>()
{
return port;
}
let uri = self.uri();
if let Some(authority) = uri.authority()
&& let Some(port_str) = authority.as_str().split(':').nth(1)
&& let Ok(port) = port_str.parse::<u16>()
{
return port;
}
if self.scheme().eq("https") {
return 443;
}
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(&self) -> Vec<(String, String)> {
form_urlencoded::parse(self.uri().query().unwrap_or("").as_bytes())
.into_owned()
.collect()
}
pub fn to_http_request(&self) -> http::Request<Bytes> {
self.into()
}
pub fn query_param_length(&self) -> usize {
form_urlencoded::parse(self.uri().query().unwrap_or("").as_bytes()).count()
}
pub fn body(&self) -> &HttpMockBytes {
&self.body
}
pub fn body_string(&self) -> String {
self.body.to_string()
}
pub fn body_ref(&self) -> &[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)
}
}
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| Error::RequestConversion(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 + Into<HttpMockBytes>,
{
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 = value.body().clone().into();
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: Into<HttpMockBytes>,
{
fn from(req: http::Request<B>) -> Self {
let scheme = req
.extensions()
.get::<RequestMetadata>()
.expect("request metadata was not added to the request")
.scheme
.to_string();
let headers = http_headers_to_vec(&req).expect("invalid headers in http::Request");
let (parts, body) = req.into_parts();
HttpMockRequest::new(
scheme,
parts.uri.to_string(),
parts.method.to_string(),
headers,
format!("{:?}", parts.version),
body.into(),
)
}
}
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()
}
pub fn to_http_response(&self) -> Result<http::Response<Bytes>, Error> {
http::Response::<Bytes>::try_from(self)
}
}
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::ResponseConversion("missing status".into()))?;
let status = http::StatusCode::from_u16(raw_status)
.map_err(|_| Error::ResponseConversion(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::ResponseConversion(format!("invalid header name: {}", name)))?;
let header_value = http::header::HeaderValue::try_from(value.clone()).map_err(|_| {
Error::ResponseConversion(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::ResponseConversion(format!("http build error: {}", e)))
}
}
impl<B> TryFrom<&http::Response<B>> for HttpMockResponse
where
B: Clone + Into<HttpMockBytes>,
{
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::ResponseConversion(format!("non-utf8 header value for '{}'", name)))?;
headers.push((name, val.to_string()));
}
let body = resp.body().clone().into();
Ok(HttpMockResponse {
status: Some(resp.status().as_u16()),
headers: Some(headers),
body: Some(body),
})
}
}
impl<B> From<http::Response<B>> for HttpMockResponse
where
B: Into<HttpMockBytes>,
{
fn from(resp: http::Response<B>) -> Self {
let (parts, body) = resp.into_parts();
let headers = parts
.headers
.iter()
.map(|(name, value)| {
(
name.as_str().to_string(),
value
.to_str()
.unwrap_or_else(|_| panic!("non-UTF-8 value for response header '{name}'"))
.to_string(),
)
})
.collect();
Self {
status: Some(parts.status.as_u16()),
headers: Some(headers),
body: Some(body.into()),
}
}
}
#[cfg(test)]
mod http_body_conversion_tests {
use super::*;
struct NonCloneBody(&'static [u8]);
impl From<NonCloneBody> for HttpMockBytes {
fn from(body: NonCloneBody) -> Self {
body.0.into()
}
}
#[test]
fn owned_message_conversions_accept_non_clone_bodies() {
let mut request = http::Request::new(NonCloneBody(b"request"));
request.extensions_mut().insert(RequestMetadata::new("http"));
let request: HttpMockRequest = request.into();
let response = http::Response::new(NonCloneBody(b"response"));
let response: HttpMockResponse = response.into();
assert_eq!(request.body().as_ref(), b"request");
assert_eq!(response.body.as_ref().unwrap().as_ref(), b"response");
}
#[test]
fn borrowed_message_conversions_still_accept_clone_bodies() {
let mut request = http::Request::new(Vec::from(b"request"));
request.extensions_mut().insert(RequestMetadata::new("http"));
let request = HttpMockRequest::try_from(&request).unwrap();
let response = http::Response::new(Vec::from(b"response"));
let response = HttpMockResponse::try_from(&response).unwrap();
assert_eq!(request.body().as_ref(), b"request");
assert_eq!(response.body.as_ref().unwrap().as_ref(), b"response");
}
#[test]
fn unit_is_an_empty_http_body() {
let response: HttpMockResponse = http::Response::new(()).into();
assert!(response.body.unwrap().is_empty());
}
}
#[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, Default)]
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<ResponseCallback>,
}
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| Error::HeaderDeserialization(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 base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use bytes::Bytes;
use serde::{Deserialize, Deserializer, Serializer};
use crate::common::util::HttpMockBytes;
pub fn serialize<T, S>(bytes: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
S: Serializer,
{
match bytes {
Some(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()
}
}
mod regex_string {
use serde::{Deserialize, Deserializer, Serializer, de::Error};
pub fn serialize<S: Serializer>(value: ®ex::Regex, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(value.as_str())
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<regex::Regex, D::Error> {
let pattern = std::borrow::Cow::<str>::deserialize(deserializer)?;
pattern.parse().map_err(Error::custom)
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct HttpMockRegex(#[serde(with = "regex_string")] 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, Default)]
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<RequestPredicate>>, #[serde(skip)]
pub is_false: Option<Vec<RequestPredicate>>, }
#[derive(Serialize, Deserialize, Clone)]
pub struct MockDefinition {
pub request: RequestRequirements,
pub response: MockServerHttpResponse,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub up_to_n_times: Option<usize>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ActiveMock {
pub id: usize,
pub call_counter: usize,
pub definition: MockDefinition,
pub is_static: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub up_to_n_times: Option<NonZero<usize>>,
}
impl ActiveMock {
pub fn is_exhausted(&self) -> bool {
self.up_to_n_times.is_some_and(|limit| self.call_counter >= limit.get())
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct UpToNTimesConfig {
pub up_to_n_times: usize,
}
#[cfg(feature = "proxy")]
#[derive(Serialize, Deserialize, Clone)]
pub struct ActiveForwardingRule {
pub id: usize,
pub config: ForwardingRuleConfig,
}
#[cfg(feature = "proxy")]
#[derive(Serialize, Deserialize, Clone)]
pub struct ActiveProxyRule {
pub id: usize,
pub config: ProxyRuleConfig,
}
#[cfg(feature = "record")]
#[derive(Serialize, Deserialize, Clone)]
pub struct ActiveRecording {
pub id: usize,
pub config: RecordingRuleConfig,
pub mocks: Vec<MockDefinition>,
}
#[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>,
}
#[cfg(feature = "record")]
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct RecordingRuleConfig {
pub request_requirements: RequestRequirements,
pub record_headers: Vec<String>,
pub record_response_delays: bool,
}
#[cfg(feature = "proxy")]
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct ProxyRuleConfig {
pub request_requirements: RequestRequirements,
pub request_header: Vec<(String, String)>,
}
#[cfg(feature = "proxy")]
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct ForwardingRuleConfig {
pub target_base_url: String,
pub request_requirements: RequestRequirements,
pub request_header: Vec<(String, String)>,
}
#[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()
.unwrap_or_else(|error| panic!("Cannot parse HTTP method from string {:?}: {:?}", value, error))
}
}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}