use std::{net::SocketAddr, sync::Arc};
use bytes::Bytes;
use http::{Request, StatusCode};
use serde::de::DeserializeOwned;
#[cfg(feature = "proxy")]
use crate::common::data::{ActiveForwardingRule, ActiveProxyRule, ForwardingRuleConfig, ProxyRuleConfig};
#[cfg(feature = "record")]
use crate::common::data::{ActiveRecording, RecordingRuleConfig};
use crate::{
api::adapter::{
MockServerAdapter, ServerAdapterError,
ServerAdapterError::{
InvalidMockDefinitionError, JsonDeserializationError, JsonSerializationError, UpstreamError,
},
},
common::{
data::{
ActiveMock, ClosestMatch, MockDefinition, MockServerHttpResponse, RequestRequirements, UpToNTimesConfig,
},
http::HttpMockHttpClient,
},
};
pub(in crate::api) struct RemoteMockServerAdapter {
addr: SocketAddr,
http_client: Arc<HttpMockHttpClient>,
}
impl RemoteMockServerAdapter {
pub(in crate::api) fn new(addr: SocketAddr, http_client: Arc<HttpMockHttpClient>) -> Self {
Self { addr, http_client }
}
fn validate_request_requirements(&self, requirements: &RequestRequirements) -> Result<(), ServerAdapterError> {
match requirements.is_true {
Some(_) => Err(InvalidMockDefinitionError(
"Anonymous function request matchers are not supported when using a remote mock server".to_string(),
)),
None => Ok(()),
}
}
fn validate_response(&self, response: &MockServerHttpResponse) -> Result<(), ServerAdapterError> {
match response.respond_with {
Some(_) => Err(InvalidMockDefinitionError(
"Dynamic responders are not supported by remote/standalone servers".to_string(),
)),
None => Ok(()),
}
}
fn build_request(
&self,
method: &str,
path: &str,
json_body: Option<String>,
) -> Result<Request<Bytes>, ServerAdapterError> {
let mut builder = Request::builder()
.method(method)
.uri(format!("http://{}/__httpmock__/{}", self.addr, path));
let body = match json_body {
Some(json) => {
builder = builder.header("content-type", "application/json");
Bytes::from(json)
}
None => Bytes::new(),
};
builder.body(body).map_err(|e| UpstreamError(e.to_string()))
}
async fn send_checked(
&self,
request: Request<Bytes>,
expected_status: StatusCode,
context: &str,
) -> Result<String, ServerAdapterError> {
let (status, body) = self.do_request(request).await?;
if status != expected_status.as_u16() {
return Err(UpstreamError(format!(
"Could not {}. Expected response status {} but was {} (response body = '{}')",
context,
expected_status.as_u16(),
status,
body
)));
}
Ok(body)
}
async fn request_json<T: DeserializeOwned>(
&self,
method: &str,
path: &str,
json_body: Option<String>,
expected_status: StatusCode,
context: &str,
) -> Result<T, ServerAdapterError> {
let request = self.build_request(method, path, json_body)?;
let body = self.send_checked(request, expected_status, context).await?;
serde_json::from_str(&body).map_err(JsonDeserializationError)
}
async fn request_empty(
&self,
method: &str,
path: &str,
json_body: Option<String>,
expected_status: StatusCode,
context: &str,
) -> Result<(), ServerAdapterError> {
let request = self.build_request(method, path, json_body)?;
self.send_checked(request, expected_status, context).await?;
Ok(())
}
async fn do_request(&self, req: Request<Bytes>) -> Result<(u16, String), ServerAdapterError> {
let (code, body_bytes) = self.do_request_raw(req).await?;
let body = String::from_utf8(body_bytes.to_vec()).map_err(|e| UpstreamError(e.to_string()))?;
Ok((code, body))
}
async fn do_request_raw(&self, req: Request<Bytes>) -> Result<(u16, Bytes), ServerAdapterError> {
let response = self
.http_client
.send(req)
.await
.map_err(|e| UpstreamError(e.to_string()))?;
Ok((response.status().as_u16(), response.body().clone()))
}
}
impl MockServerAdapter for RemoteMockServerAdapter {
fn host(&self) -> String {
self.addr.ip().to_string()
}
fn port(&self) -> u16 {
self.addr.port()
}
fn address(&self) -> &SocketAddr {
&self.addr
}
async fn reset(&self) -> Result<(), ServerAdapterError> {
self.request_empty("DELETE", "state", None, StatusCode::NO_CONTENT, "reset the mock server")
.await
}
async fn create_mock(&self, mock: &MockDefinition) -> Result<ActiveMock, ServerAdapterError> {
self.validate_request_requirements(&mock.request)?;
self.validate_response(&mock.response)?;
let json = serde_json::to_string(mock).map_err(JsonSerializationError)?;
self.request_json("POST", "mocks", Some(json), StatusCode::CREATED, "create mock")
.await
}
async fn fetch_mock(&self, mock_id: usize) -> Result<ActiveMock, ServerAdapterError> {
self.request_json(
"GET",
&format!("mocks/{}", mock_id),
None,
StatusCode::OK,
"fetch mock from the mock server",
)
.await
}
async fn delete_mock(&self, mock_id: usize) -> Result<(), ServerAdapterError> {
self.request_empty(
"DELETE",
&format!("mocks/{}", mock_id),
None,
StatusCode::NO_CONTENT,
"delete mock from the mock server",
)
.await
}
async fn set_up_to_n_times(&self, mock_id: usize, count: usize) -> Result<(), ServerAdapterError> {
let config = UpToNTimesConfig { up_to_n_times: count };
let json = serde_json::to_string(&config).map_err(JsonSerializationError)?;
let request = self.build_request("PUT", &format!("mocks/{}/up_to_n_times", mock_id), Some(json))?;
let (status, body) = self.do_request(request).await?;
if status != StatusCode::INTERNAL_SERVER_ERROR.as_u16()
&& let Ok(mock) = self.fetch_mock(mock_id).await
&& mock.up_to_n_times.is_none()
{
return Err(UpstreamError(
"The mock server does not support up_to_n_times. Upgrade the mock server to use it.".to_string(),
));
}
if status != StatusCode::NO_CONTENT.as_u16() {
return Err(UpstreamError(format!(
"Could not set the up_to_n_times limit on the mock. Expected response status {} but was {} \
(response body = '{}')",
StatusCode::NO_CONTENT.as_u16(),
status,
body
)));
}
Ok(())
}
async fn verify(&self, requirements: &RequestRequirements) -> Result<Option<ClosestMatch>, ServerAdapterError> {
let json = serde_json::to_string(requirements).map_err(JsonSerializationError)?;
let request = self.build_request("POST", "verify", Some(json))?;
let (status, body) = self.do_request(request).await?;
if status == StatusCode::NOT_FOUND {
return Ok(None);
}
if status != StatusCode::OK.as_u16() {
return Err(UpstreamError(format!(
"Could not verify mock. Expected response status 200 but was {} (response body = '{}')",
status, body
)));
}
let response: ClosestMatch = serde_json::from_str(&body).map_err(JsonDeserializationError)?;
Ok(Some(response))
}
#[cfg(feature = "proxy")]
async fn create_forwarding_rule(
&self,
config: ForwardingRuleConfig,
) -> Result<ActiveForwardingRule, ServerAdapterError> {
self.validate_request_requirements(&config.request_requirements)?;
let json = serde_json::to_string(&config).map_err(JsonSerializationError)?;
self.request_json(
"POST",
"forwarding_rules",
Some(json),
StatusCode::CREATED,
"create forwarding rule",
)
.await
}
#[cfg(feature = "proxy")]
async fn delete_forwarding_rule(&self, id: usize) -> Result<(), ServerAdapterError> {
self.request_empty(
"DELETE",
&format!("forwarding_rules/{}", id),
None,
StatusCode::NO_CONTENT,
"delete forwarding rule from the mock server",
)
.await
}
#[cfg(feature = "proxy")]
async fn create_proxy_rule(&self, config: ProxyRuleConfig) -> Result<ActiveProxyRule, ServerAdapterError> {
self.validate_request_requirements(&config.request_requirements)?;
let json = serde_json::to_string(&config).map_err(JsonSerializationError)?;
self.request_json(
"POST",
"proxy_rules",
Some(json),
StatusCode::CREATED,
"create proxy rule",
)
.await
}
#[cfg(feature = "proxy")]
async fn delete_proxy_rule(&self, id: usize) -> Result<(), ServerAdapterError> {
self.request_empty(
"DELETE",
&format!("proxy_rules/{}", id),
None,
StatusCode::NO_CONTENT,
"delete proxy rule from the mock server",
)
.await
}
#[cfg(feature = "record")]
async fn create_recording(&self, config: RecordingRuleConfig) -> Result<ActiveRecording, ServerAdapterError> {
self.validate_request_requirements(&config.request_requirements)?;
let json = serde_json::to_string(&config).map_err(JsonSerializationError)?;
self.request_json(
"POST",
"recordings",
Some(json),
StatusCode::CREATED,
"create recording",
)
.await
}
#[cfg(feature = "record")]
async fn delete_recording(&self, id: usize) -> Result<(), ServerAdapterError> {
self.request_empty(
"DELETE",
&format!("recordings/{}", id),
None,
StatusCode::NO_CONTENT,
"delete recording from the mock server",
)
.await
}
#[cfg(feature = "record")]
async fn export_recording(&self, id: usize) -> Result<Option<Bytes>, ServerAdapterError> {
let request = self.build_request("GET", &format!("recordings/{}", id), None)?;
let (status, body) = self.do_request_raw(request).await?;
if status == StatusCode::NOT_FOUND {
return Ok(None);
} else if status != StatusCode::OK.as_u16() {
return Err(UpstreamError(format!(
"Could not fetch mock from the mock server. Expected response status 200 but was {}",
status
)));
}
Ok(Some(body))
}
#[cfg(feature = "record")]
async fn create_mocks_from_recording(
&self,
recording_file_content: &str,
) -> Result<Vec<usize>, ServerAdapterError> {
let request = Request::builder()
.method("POST")
.uri(format!("http://{}/__httpmock__/recordings", self.addr))
.body(Bytes::from(recording_file_content.to_owned()))
.map_err(|e| UpstreamError(e.to_string()))?;
let body = self
.send_checked(request, StatusCode::OK, "create mocks from recording")
.await?;
serde_json::from_str(&body).map_err(JsonDeserializationError)
}
}