use crate::{
matchers::{and, or},
server::ServerAdapter,
EasyHttpMock, HttpMockResult,
};
use bytes::Bytes;
use caramelo::{MatchType, Matcher, TypedMatcher};
use http::{request::Parts, HeaderMap, Method, StatusCode, Uri};
use std::{collections::HashMap, fmt::Debug, sync::Arc};
pub struct MockState {
inner: Arc<Mock>,
}
impl MockState {
#[inline]
pub fn new(mock: Mock) -> Self {
Self { inner: Arc::new(mock) }
}
#[inline]
pub fn inner(&self) -> Arc<Mock> {
self.inner.clone()
}
pub async fn use_on<S: ServerAdapter>(
self,
server: &mut EasyHttpMock<S>,
) -> HttpMockResult<()> {
server
.register_mock(self)
.await?;
Ok(())
}
}
pub struct Mock {
request: RequestMock,
}
impl Mock {
#[inline]
pub fn of(request: RequestMock) -> MockState {
MockState::new(Self { request })
}
#[inline]
pub fn request(&self) -> &RequestMock {
&self.request
}
}
#[inline]
pub fn given(matcher: impl TypedMatcher<Request> + Send + Sync + 'static) -> RequestMock {
RequestMock { matcher: Arc::from(matcher), respond: None }
}
pub struct RequestMock {
matcher: Arc<dyn TypedMatcher<Request> + Send + Sync + 'static>,
respond: Option<Respond>,
}
impl RequestMock {
#[inline]
pub fn matcher(&self) -> &Arc<dyn TypedMatcher<Request> + Send + Sync + 'static> {
&self.matcher
}
#[inline]
pub fn respond(&self) -> Option<&Respond> {
self.respond
.as_ref()
}
#[inline]
pub fn will_return(mut self, respond: Respond) -> Self {
self.respond = Some(respond);
self
}
}
impl Matcher<Request> for Arc<dyn TypedMatcher<Request> + Send + Sync + 'static> {
fn matches(&self, request: &Request) -> bool {
self.as_ref()
.matches(request)
}
fn description(&self) -> String {
self.as_ref()
.description()
}
}
impl TypedMatcher<Request> for Arc<dyn TypedMatcher<Request> + Send + Sync + 'static> {
fn matcher_type(&self) -> MatchType {
self.as_ref()
.matcher_type()
}
}
pub trait AsyncMatcherExt<T>: TypedMatcher<T> + Sized + 'static
where
T: Send + Sync + 'static,
{
fn and<M>(self, matcher: M) -> Arc<dyn TypedMatcher<T> + Send + Sync + 'static>
where
Self: Send + Sync,
M: TypedMatcher<T> + Send + Sync + 'static,
{
and(vec![Arc::new(self), Arc::new(matcher)])
}
fn or<M>(self, matcher: M) -> Arc<dyn TypedMatcher<T> + Send + Sync + 'static>
where
Self: Send + Sync,
M: TypedMatcher<T> + Send + Sync + 'static,
{
or(vec![Arc::new(self), Arc::new(matcher)])
}
}
impl<M, T> AsyncMatcherExt<T> for M
where
M: TypedMatcher<T> + Send + Sync + 'static,
T: Send + Sync + 'static,
{
}
pub trait StatusCodeExt {
fn respond(self) -> RespondBuilder;
}
impl StatusCodeExt for StatusCode {
fn respond(self) -> RespondBuilder {
RespondBuilder { status_code: self, headers: HashMap::new() }
}
}
#[derive(Debug)]
pub struct RequestBuilder {
uri: Uri,
query_params: Option<HashMap<String, String>>,
method: http::Method,
version: http::Version,
headers: http::HeaderMap,
body: Option<Bytes>,
}
impl RequestBuilder {
pub fn uri(self, uri: Uri) -> Self {
Self { uri, ..self }
}
pub fn query_params(self, query_params: HashMap<String, String>) -> Self {
Self { query_params: Some(query_params), ..self }
}
pub fn method(self, method: http::Method) -> Self {
Self { method, ..self }
}
pub fn version(self, version: http::Version) -> Self {
Self { version, ..self }
}
pub fn header<K>(self, name: K, value: &str) -> Self
where
K: http::header::IntoHeaderName,
{
let mut headers = self.headers;
headers.insert(
name,
value
.parse()
.unwrap(),
);
Self { headers, ..self }
}
pub fn empty(self) -> Result<Request, http::Error> {
Ok(Request {
method: self.method,
version: self.version,
query_params: self.query_params,
uri: self.uri,
headers: self.headers,
body: None,
})
}
pub fn body(self) -> Result<Request, http::Error> {
Ok(Request {
method: self.method,
version: self.version,
query_params: self.query_params,
uri: self.uri,
headers: self.headers,
body: self.body,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Request {
method: Method,
uri: Uri,
version: http::Version,
headers: HeaderMap,
query_params: Option<HashMap<String, String>>,
body: Option<Bytes>,
}
impl Request {
#[inline]
pub fn from_parts(parts: Parts) -> Request {
let query_params = parts
.uri
.query()
.map(|q| {
q.split('&')
.filter_map(|pair| {
pair.split_once('=')
.map(|(k, v)| (k.to_string(), v.to_string()))
})
.collect()
});
Request {
method: parts.method,
uri: parts.uri,
version: parts.version,
headers: parts.headers,
query_params,
body: None,
}
}
fn builder(method: http::Method, uri: Uri) -> RequestBuilder {
RequestBuilder {
method,
version: http::Version::HTTP_11,
uri,
headers: http::HeaderMap::new(),
body: None,
query_params: None,
}
}
pub fn get(uri: Uri) -> RequestBuilder {
Self::builder(http::Method::GET, uri)
}
pub fn post(uri: Uri) -> RequestBuilder {
Self::builder(http::Method::POST, uri)
}
pub fn put(uri: Uri) -> RequestBuilder {
Self::builder(http::Method::PUT, uri)
}
pub fn delete(uri: Uri) -> RequestBuilder {
Self::builder(http::Method::DELETE, uri)
}
pub fn patch(uri: Uri) -> RequestBuilder {
Self::builder(http::Method::PATCH, uri)
}
pub fn head(uri: Uri) -> RequestBuilder {
Self::builder(http::Method::HEAD, uri)
}
pub fn options(uri: Uri) -> RequestBuilder {
Self::builder(http::Method::OPTIONS, uri)
}
pub fn trace(uri: Uri) -> RequestBuilder {
Self::builder(http::Method::TRACE, uri)
}
pub fn connect(uri: Uri) -> RequestBuilder {
Self::builder(http::Method::CONNECT, uri)
}
#[inline]
pub fn path(&self) -> &Uri {
&self.uri
}
#[inline]
pub fn method(&self) -> &Method {
&self.method
}
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
#[inline]
pub fn version(&self) -> &http::Version {
&self.version
}
#[inline]
pub fn query_params(&self) -> &Option<HashMap<String, String>> {
&self.query_params
}
#[inline]
pub fn body(&self) -> &Option<Bytes> {
&self.body
}
}
pub struct RespondBuilder {
status_code: StatusCode,
headers: HashMap<String, String>,
}
impl RespondBuilder {
#[inline]
pub fn with_status(mut self, status: StatusCode) -> Self {
self.status_code = status;
self
}
#[inline]
pub fn with_header(mut self, key: &str, value: &str) -> Self {
self.headers
.insert(key.to_string(), value.to_string());
self
}
#[inline]
pub fn with_headers(mut self, entries: &[(&str, &str)]) -> Self {
for (key, value) in entries {
self.headers
.insert(key.to_string(), value.to_string());
}
self
}
#[inline]
pub fn empty(self) -> Respond {
self.no_body()
}
#[inline]
pub fn no_body(self) -> Respond {
Respond { status_code: self.status_code, headers: self.headers, body: Bytes::new() }
}
#[inline]
pub fn with_body(self, body: &[u8]) -> Respond {
Respond {
status_code: self.status_code,
headers: self.headers,
body: Bytes::from(body.to_vec()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Respond {
status_code: StatusCode,
headers: HashMap<String, String>,
body: Bytes,
}
impl Respond {
#[inline]
pub fn builder() -> RespondBuilder {
RespondBuilder { status_code: StatusCode::OK, headers: HashMap::new() }
}
#[inline]
pub fn status_code(&self) -> StatusCode {
self.status_code
}
#[inline]
pub fn headers(&self) -> HashMap<String, String> {
self.headers.clone()
}
#[inline]
pub fn body(&self) -> Bytes {
self.body.clone()
}
}