noxious_client/
proxy.rs

1use crate::client::get_error_body;
2use crate::client::Result;
3use noxious::{
4    proxy::{ProxyConfig, ProxyWithToxics},
5    toxic::{StreamDirection, Toxic, ToxicKind},
6};
7use reqwest::{Client as HttpClient, StatusCode};
8
9/// A proxy object returned by the [`Client`](Client).
10/// To manipulate this proxy and manipulate the toxics, you can call methods on
11/// this object.
12#[derive(Debug, Clone, PartialEq)]
13pub struct Proxy {
14    base_url: String,
15    created: bool,
16    /// Contains the proxy listen and upstream address, name. You can mutate them
17    /// and call `.save()` to update the proxy.
18    pub config: ProxyConfig,
19    toxics: Vec<Toxic>,
20}
21
22impl Proxy {
23    /// Save saves changes to a proxy such as its enabled status or upstream port.
24    /// Note: this does not update the toxics
25    pub async fn save(&mut self) -> Result<()> {
26        let request = if self.created {
27            HttpClient::new()
28                .post(self.base_url.clone() + "/proxies/" + &self.config.name)
29                .json(&self.config)
30        } else {
31            HttpClient::new()
32                .post(self.base_url.clone() + "/proxies")
33                .json(&self.config)
34        };
35        let response = request.send().await?;
36        if response.status().is_success() {
37            self.created = true;
38            Ok(())
39        } else {
40            let expected_status = if self.created {
41                StatusCode::OK
42            } else {
43                StatusCode::CREATED
44            };
45            Err(get_error_body(response, expected_status).await)
46        }
47    }
48
49    /// Enable a proxy again after it has been disabled
50    pub async fn enable(&mut self) -> Result<()> {
51        self.config.enabled = true;
52        self.save().await
53    }
54
55    /// Disable a proxy so that no connections can pass through. This will drop all active connections.
56    pub async fn disable(&mut self) -> Result<()> {
57        self.config.enabled = false;
58        self.save().await
59    }
60
61    /// Returns whether this proxy is enabled or not
62    pub fn is_enabled(&self) -> bool {
63        self.config.enabled
64    }
65
66    /// Give this proxy a new name, save it.
67    pub async fn change_name(&mut self, new_name: &str) -> Result<()> {
68        let old_name = self.config.name.clone();
69        self.config.name = new_name.to_owned();
70        let res = HttpClient::new()
71            .post(self.base_url.clone() + "/proxies/" + &old_name)
72            .json(&self.config)
73            .send()
74            .await?;
75        if res.status().is_success() {
76            Ok(())
77        } else {
78            Err(get_error_body(res, StatusCode::OK).await)
79        }
80    }
81
82    /// Delete a proxy complete and close all existing connections through it. All information about
83    /// the proxy such as listen port and active toxics will be deleted as well. If you just wish to
84    /// stop and later enable a proxy, use `enable()` and `disable()`.
85    pub async fn delete(self) -> Result<()> {
86        let res = HttpClient::new()
87            .delete(self.base_url.clone() + "/proxies/" + &self.config.name)
88            .send()
89            .await?;
90        if res.status().is_success() {
91            Ok(())
92        } else {
93            Err(get_error_body(res, StatusCode::NO_CONTENT).await)
94        }
95    }
96
97    /// Returns a map of all active toxics and their attributes
98    pub async fn toxics(&self) -> Result<Vec<Toxic>> {
99        let res = HttpClient::new()
100            .get(self.base_url.clone() + "/proxies/" + &self.config.name + "/toxics")
101            .send()
102            .await?;
103
104        if res.status().is_success() {
105            Ok(res.json::<Vec<Toxic>>().await?)
106        } else {
107            Err(get_error_body(res, StatusCode::OK).await)
108        }
109    }
110
111    /// Add a new toxic to this proxy.
112
113    /// # Example
114    /// ```ignore
115    /// use noxious_client::{Client, Toxic, ToxicKind, StreamDirection};
116    ///
117    /// #[tokio::main]
118    /// async fn main() {
119    ///     let toxic = Toxic {
120    ///         kind: ToxicKind::Latency { latency: 40, jitter: 5 },
121    ///         name: "myProxy_latency".to_owned(),
122    ///         toxicity: 0.9,
123    ///         direction: StreamDirection::Upstream,
124    ///     };
125    ///
126    ///     let client = Client::new("127.0.0.1:8474");
127    ///     let result = client.add_toxic(&toxic).await;
128    /// }
129    /// ```
130    ///
131    pub async fn add_toxic(&self, toxic: &Toxic) -> Result<Toxic> {
132        let res = HttpClient::new()
133            .post(self.base_url.clone() + "/proxies/" + &self.config.name + "/toxics")
134            .json(toxic)
135            .send()
136            .await?;
137
138        if res.status().is_success() {
139            Ok(res.json::<Toxic>().await?)
140        } else {
141            Err(get_error_body(res, StatusCode::OK).await)
142        }
143    }
144
145    /// Updates a toxic with the given name
146    /// If toxicity is below zero, it will be sent as 0
147    pub async fn update_toxic(
148        &self,
149        name: &str,
150        toxicity: f32,
151        kind: ToxicKind,
152        direction: StreamDirection,
153    ) -> Result<Toxic> {
154        let toxicity: f32 = if toxicity < 0.0 { 0.0 } else { toxicity };
155        let toxic = Toxic {
156            kind,
157            name: name.to_owned(),
158            toxicity,
159            direction,
160        };
161        let res = HttpClient::new()
162            .post(self.base_url.clone() + "/proxies/" + &self.config.name + "/toxics/" + name)
163            .json(&toxic)
164            .send()
165            .await?;
166
167        if res.status().is_success() {
168            Ok(res.json::<Toxic>().await?)
169        } else {
170            Err(get_error_body(res, StatusCode::OK).await)
171        }
172    }
173
174    /// Removes a toxic with the given name
175    pub async fn remove_toxic(&self, name: &str) -> Result<()> {
176        let res = HttpClient::new()
177            .delete(self.base_url.clone() + "/proxies/" + &self.config.name + "/toxics/" + name)
178            .send()
179            .await?;
180        if res.status().is_success() {
181            Ok(())
182        } else {
183            Err(get_error_body(res, StatusCode::NO_CONTENT).await)
184        }
185    }
186
187    pub(crate) fn new(base_url: &str, name: &str, listen: &str, upstream: &str) -> Proxy {
188        Proxy {
189            base_url: base_url.to_owned(),
190            created: false,
191            config: ProxyConfig {
192                name: name.to_owned(),
193                listen: listen.to_owned(),
194                upstream: upstream.to_owned(),
195                enabled: true,
196                rand_seed: None,
197            },
198            toxics: Vec::new(),
199        }
200    }
201
202    #[doc(hidden)]
203    pub fn from_proxy_with_toxics(base_url: &str, obj: ProxyWithToxics) -> Proxy {
204        Proxy {
205            base_url: base_url.to_owned(),
206            created: true,
207            config: obj.proxy,
208            toxics: obj.toxics,
209        }
210    }
211}