use std::collections::HashMap;
use crate::{
error::{ApiErrorResponse, ClientError},
proxy::Proxy,
};
use noxious::proxy::{ProxyConfig, ProxyWithToxics};
use reqwest::{Client as HttpClient, Response, StatusCode};
#[derive(Debug)]
pub struct Client {
base_url: String,
}
pub type Result<T> = std::result::Result<T, ClientError>;
impl Client {
pub fn new(url: &str) -> Client {
if url.starts_with("https://") {
panic!("the toxiproxy client does not support https");
}
let base_url = if !url.starts_with("http://") {
format!("http://{}", url)
} else {
url.to_owned()
};
Client { base_url }
}
pub async fn proxy(&self, name: &str) -> Result<Proxy> {
let res = HttpClient::new()
.get(self.base_url.clone() + "/proxies/" + name)
.send()
.await?;
if res.status().is_success() {
Ok(Proxy::from_proxy_with_toxics(
&self.base_url,
res.json::<ProxyWithToxics>().await?,
))
} else {
Err(get_error_body(res, StatusCode::OK).await)
}
}
pub async fn proxies(&self) -> Result<HashMap<String, Proxy>> {
let res = HttpClient::new()
.get(self.base_url.clone() + "/proxies")
.send()
.await?;
if res.status().is_success() {
Ok(res
.json::<HashMap<String, ProxyWithToxics>>()
.await?
.into_iter()
.map(|(name, proxy)| (name, Proxy::from_proxy_with_toxics(&self.base_url, proxy)))
.collect())
} else {
Err(get_error_body(res, StatusCode::OK).await)
}
}
pub async fn create_proxy(&self, name: &str, listen: &str, upstream: &str) -> Result<Proxy> {
let mut proxy = Proxy::new(&self.base_url, name, listen, upstream);
proxy.save().await?;
Ok(proxy)
}
pub async fn populate(&self, proxies: &[ProxyConfig]) -> Result<Vec<Proxy>> {
let res = HttpClient::new()
.post(self.base_url.clone() + "/populate")
.json(proxies)
.send()
.await?;
if res.status().is_success() {
Ok(res
.json::<Vec<ProxyWithToxics>>()
.await?
.into_iter()
.map(|item| Proxy::from_proxy_with_toxics(&self.base_url, item))
.collect::<Vec<Proxy>>())
} else {
Err(get_error_body(res, StatusCode::CREATED).await)
}
}
pub async fn reset_state(&self) -> Result<()> {
let res = HttpClient::new()
.post(self.base_url.clone() + "/reset")
.send()
.await?;
if res.status().is_success() {
Ok(())
} else {
Err(get_error_body(res, StatusCode::NO_CONTENT).await)
}
}
}
pub(crate) async fn get_error_body(res: Response, expected_status: StatusCode) -> ClientError {
let code = res.status();
if let Ok(api_error) = res.json::<ApiErrorResponse>().await {
ClientError::ApiError(api_error)
} else {
ClientError::UnexpectedStatusCode(code.into(), expected_status.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn client_does_not_allow_https() {
let _client = Client::new("https://blahblah");
}
#[test]
fn client_adds_protocol() {
let client = Client::new("blahblah");
assert_eq!("http://blahblah", client.base_url);
}
}