use std::{collections::HashMap, fmt::Debug, sync::Arc};
use bytes::Bytes;
use caramelo::{expect, matchers::and, TypedMatcher};
use http::{request::Parts, HeaderMap, Method, StatusCode, Uri};
use crate::{matchers::HttpMatcher, server::ServerAdapter, EasyHttpMock, HttpMockResult};
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<Self> {
server
.register_mock(&self)
.await?;
Ok(self)
}
}
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 match_with(&self, request: Request) {
let expect = expect(request);
let matchers: Vec<Box<dyn TypedMatcher<Request>>> = self
.request
.matchers
.iter()
.fold(Vec::new(), |mut acc, m| {
acc.push(Box::new(m.clone()));
acc
});
expect.to_match(and(matchers));
}
}
#[inline]
pub fn given(matcher: HttpMatcher) -> RequestMock {
RequestMock { matchers: vec![matcher], respond: None }
}
pub struct RequestMock {
matchers: Vec<HttpMatcher>,
respond: Option<Respond>,
}
impl RequestMock {
#[inline]
pub fn and(mut self, matcher: HttpMatcher) -> Self {
self.matchers
.push(matcher);
self
}
#[inline]
pub fn matchers(&self) -> &Vec<HttpMatcher> {
&self.matchers
}
#[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
}
}
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()
}
}