use caramelo::expect;
use easyhttpmock::{
errors::{EasyHttpMockError, MockError, ServerError},
mock::{Mock, Request},
server::{generate_randon_port, PortGenerator, ServerAdapter},
HttpMockResult,
};
use http_body_util::BodyExt;
use std::sync::Arc;
use vetis_compio::{
handler_fn,
http::Response,
virtual_host::{path::HandlerPath, VirtualHostImpl},
Protocol, ServerConfig, Vetis, VetisServer,
};
pub struct VetisAdapterConfigBuilder {
hostname: String,
interface: String,
protocol: Protocol,
port: u16,
cert: Option<Vec<u8>>,
key: Option<Vec<u8>>,
ca: Option<Vec<u8>>,
}
impl VetisAdapterConfigBuilder {
pub fn hostname(mut self, hostname: &str) -> Self {
self.hostname = hostname.to_string();
self
}
pub fn interface(mut self, interface: &str) -> Self {
self.interface = interface.to_string();
self
}
pub fn protocol(mut self, protocol: Protocol) -> Self {
self.protocol = protocol;
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn cert(mut self, cert: Vec<u8>) -> Self {
self.cert = Some(cert);
self
}
pub fn key(mut self, key: Vec<u8>) -> Self {
self.key = Some(key);
self
}
pub fn ca(mut self, ca: Vec<u8>) -> Self {
self.ca = Some(ca);
self
}
pub fn build(self) -> VetisAdapterConfig {
VetisAdapterConfig {
hostname: self.hostname,
interface: self.interface,
protocol: self.protocol,
port: self.port,
cert: self.cert,
key: self.key,
ca: self.ca,
}
}
}
#[derive(Clone, PartialEq)]
pub struct VetisAdapterConfig {
hostname: String,
interface: String,
protocol: Protocol,
port: u16,
cert: Option<Vec<u8>>,
key: Option<Vec<u8>>,
ca: Option<Vec<u8>>,
}
impl Default for VetisAdapterConfig {
fn default() -> Self {
Self {
hostname: "localhost".into(),
interface: "0.0.0.0".into(),
protocol: Protocol::Http1,
port: generate_randon_port(),
cert: None,
key: None,
ca: None,
}
}
}
impl VetisAdapterConfig {
pub fn builder() -> VetisAdapterConfigBuilder {
VetisAdapterConfigBuilder {
hostname: "localhost".into(),
interface: "0.0.0.0".into(),
protocol: Protocol::Http1,
port: rand::random_range(9000..65535),
cert: None,
key: None,
ca: None,
}
}
pub fn hostname(&self) -> &String {
&self.hostname
}
pub fn interface(&self) -> &str {
&self.interface
}
pub fn port(&self) -> u16 {
self.port
}
pub fn cert(&self) -> &Option<Vec<u8>> {
&self.cert
}
pub fn key(&self) -> &Option<Vec<u8>> {
&self.key
}
pub fn ca(&self) -> &Option<Vec<u8>> {
&self.ca
}
}
impl From<VetisAdapterConfig> for ServerConfig {
fn from(config: VetisAdapterConfig) -> Self {
let listener_config = vetis_compio::ListenerConfig::builder()
.interface(&config.interface)
.protocol(config.protocol)
.port(config.port)
.build()
.expect("Failed to build listener config");
ServerConfig::builder()
.add_listener(listener_config)
.build()
.expect("Failed to build server config")
}
}
#[derive(Default)]
pub struct VetisAdapter {
server: Vetis,
config: VetisAdapterConfig,
mock: Option<Arc<Mock>>,
}
impl PortGenerator<VetisAdapter> for VetisAdapterConfigBuilder {
fn with_random_port(self) -> Self {
let port = generate_randon_port();
self.port(port)
}
}
impl ServerAdapter for VetisAdapter {
type Config = VetisAdapterConfig;
fn new(config: Self::Config) -> Result<Self, EasyHttpMockError> {
let vetis_config = config
.clone()
.into();
let server = Vetis::new(vetis_config);
Ok(Self { server, config, mock: None })
}
fn hostname(&self) -> String {
self.config
.hostname()
.clone()
}
fn base_url(&self) -> String {
let hostname = self.hostname();
if self
.config
.cert
.is_some()
{
format!("https://{}:{}", hostname, self.config.port())
} else {
format!("http://{}:{}", hostname, self.config.port())
}
}
fn config(&self) -> &Self::Config {
&self.config
}
fn config_mut(&mut self) -> &mut Self::Config {
&mut self.config
}
fn register_mock(&mut self, mock: Arc<Mock>) {
self.mock = Some(mock);
}
async fn start(&mut self) -> HttpMockResult<()> {
let mock = match self.mock.as_ref() {
Some(mocker) => mocker,
None => return Err(MockError::Notfound.into()),
};
let mock_clone = mock.clone();
let path = HandlerPath::builder()
.uri("/")
.handler(handler_fn(move |request| {
let mock = mock_clone.clone();
async move {
let (parts, body) = request.into_parts();
let mut data = Vec::<u8>::new();
let Ok(body_data) = body.collect().await else {
return Err(vetis_compio::errors::VetisError::Handler(
"Failed to collect body".to_string(),
));
};
data.extend_from_slice(&body_data.to_bytes());
expect(Request::from_parts(parts)).to_match(
mock.request()
.matcher()
.clone(),
);
let respond = mock
.request()
.respond();
if let Some(respond) = respond {
Ok(Response::builder()
.status(respond.status_code())
.bytes(&respond.body()))
} else {
Err(vetis_compio::errors::VetisError::Handler(
"Missing respond mock".to_string(),
))
}
}
}))
.build();
let hostname = self.hostname();
let host_config = vetis_compio::VirtualHostConfig::builder()
.hostname(&hostname)
.root_directory(".")
.port(self.config.port());
let host_config = if let Some(((cert, key), ca)) = self
.config
.cert
.as_ref()
.zip(
self.config
.key
.as_ref(),
)
.zip(
self.config
.ca
.as_ref(),
) {
host_config.security(
vetis_compio::SecurityConfig::builder()
.cert_from_bytes(cert.clone())
.key_from_bytes(key.clone())
.ca_cert_from_bytes(ca.clone())
.build()
.map_err(|e| EasyHttpMockError::Server(ServerError::Config(e.to_string())))?,
)
} else {
host_config
};
let host_config = host_config
.build()
.map_err(|e| EasyHttpMockError::Server(ServerError::Creation(e.to_string())))?;
let mut host = VirtualHostImpl::new(host_config);
if let Err(e) = path {
return Err(EasyHttpMockError::Server(ServerError::Creation(e.to_string())));
}
host.add_path(path.unwrap());
self.server
.add_virtual_host(host)
.await;
self.server
.start()
.await
.map_err(|e| EasyHttpMockError::Server(ServerError::Start(e.to_string())))
}
async fn stop(&mut self) -> HttpMockResult<()> {
self.server
.stop()
.await
.map_err(|e| EasyHttpMockError::Server(ServerError::Stop(e.to_string())))
}
}