1use std::collections::HashMap;
2
3use crate::{
4 error::{ApiErrorResponse, ClientError},
5 proxy::Proxy,
6};
7use noxious::proxy::{ProxyConfig, ProxyWithToxics};
8use reqwest::{Client as HttpClient, Response, StatusCode};
9
10#[derive(Debug)]
13pub struct Client {
14 base_url: String,
15}
16
17pub type Result<T> = std::result::Result<T, ClientError>;
19
20impl Client {
21 pub fn new(url: &str) -> Client {
25 if url.starts_with("https://") {
26 panic!("the toxiproxy client does not support https");
27 }
28 let base_url = if !url.starts_with("http://") {
29 format!("http://{}", url)
30 } else {
31 url.to_owned()
32 };
33 Client { base_url }
34 }
35
36 pub async fn proxy(&self, name: &str) -> Result<Proxy> {
38 let res = HttpClient::new()
39 .get(self.base_url.clone() + "/proxies/" + name)
40 .send()
41 .await?;
42 if res.status().is_success() {
43 Ok(Proxy::from_proxy_with_toxics(
44 &self.base_url,
45 res.json::<ProxyWithToxics>().await?,
46 ))
47 } else {
48 Err(get_error_body(res, StatusCode::OK).await)
49 }
50 }
51
52 pub async fn proxies(&self) -> Result<HashMap<String, Proxy>> {
54 let res = HttpClient::new()
55 .get(self.base_url.clone() + "/proxies")
56 .send()
57 .await?;
58 if res.status().is_success() {
59 Ok(res
60 .json::<HashMap<String, ProxyWithToxics>>()
61 .await?
62 .into_iter()
63 .map(|(name, proxy)| (name, Proxy::from_proxy_with_toxics(&self.base_url, proxy)))
64 .collect())
65 } else {
66 Err(get_error_body(res, StatusCode::OK).await)
67 }
68 }
69
70 pub async fn create_proxy(&self, name: &str, listen: &str, upstream: &str) -> Result<Proxy> {
73 let mut proxy = Proxy::new(&self.base_url, name, listen, upstream);
74 proxy.save().await?;
75 Ok(proxy)
76 }
77
78 pub async fn populate(&self, proxies: &[ProxyConfig]) -> Result<Vec<Proxy>> {
81 let res = HttpClient::new()
82 .post(self.base_url.clone() + "/populate")
83 .json(proxies)
84 .send()
85 .await?;
86 if res.status().is_success() {
87 Ok(res
88 .json::<Vec<ProxyWithToxics>>()
89 .await?
90 .into_iter()
91 .map(|item| Proxy::from_proxy_with_toxics(&self.base_url, item))
92 .collect::<Vec<Proxy>>())
93 } else {
94 Err(get_error_body(res, StatusCode::CREATED).await)
95 }
96 }
97
98 pub async fn reset_state(&self) -> Result<()> {
100 let res = HttpClient::new()
101 .post(self.base_url.clone() + "/reset")
102 .send()
103 .await?;
104 if res.status().is_success() {
105 Ok(())
106 } else {
107 Err(get_error_body(res, StatusCode::NO_CONTENT).await)
108 }
109 }
110}
111
112pub(crate) async fn get_error_body(res: Response, expected_status: StatusCode) -> ClientError {
113 let code = res.status();
114 if let Ok(api_error) = res.json::<ApiErrorResponse>().await {
115 ClientError::ApiError(api_error)
116 } else {
117 ClientError::UnexpectedStatusCode(code.into(), expected_status.into())
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 #[should_panic]
127 fn client_does_not_allow_https() {
128 let _client = Client::new("https://blahblah");
129 }
130
131 #[test]
132 fn client_adds_protocol() {
133 let client = Client::new("blahblah");
134 assert_eq!("http://blahblah", client.base_url);
135 }
136}