upcloud_rs/
lib.rs

1//! Client library for the <https://www.upcloud.com/> API which
2//! is documented at <https://developers.upcloud.com/>
3//!
4//! # Example blocking
5//! It needs to have the feature "blocking" enabled.
6//! ```toml
7//! upcloud_rs = { version = "*", features = ["blocking"] }
8//! ```
9//! ```ignore
10//! use upcloud_rs::{UpcloudApi, UpcloudError};
11//!
12//! fn main() -> Result<(), UpcloudError> {
13//!     let api = UpcloudApi::new("username", "password");
14//!     
15//!     let account = api.get_account_info()?;
16//!     println!("ACCOUNT: {:?}", account);
17//!     
18//!     let account_list = api.get_account_list()?;
19//!     println!("ACCOUNT LIST: {:?}", account_list);
20//!     
21//!     let prices = api.get_prices()?;
22//!     println!("PRICES: {:#?}", prices);
23//!     
24//!     let zones = api.get_zones()?;
25//!     println!("ZONES: {:#?}", zones);
26//!     Ok(())
27//! }
28//! ```
29//!
30//! # Example async
31//! ```toml
32//! upcloud_rs = { version = "*" }
33//! ```
34//! ```no_run
35//! use upcloud_rs::{UpcloudApi, UpcloudError};
36//!
37//! #[async_std::main]
38//! async fn main() -> Result<(), UpcloudError> {
39//!     let api = UpcloudApi::new("username", "password");
40//!     let account = api.get_account_info_async().await?;
41//!     println!("ACCOUNT: {:?}", account);
42//!     
43//!     let account_list = api.get_account_list_async().await?;
44//!     println!("ACCOUNT LIST: {:?}", account_list);
45//!     
46//!     let prices = api.get_prices_async().await?;
47//!     println!("PRICES: {:#?}", prices);
48//!     Ok(())
49//! }
50//! ```
51//! ## Features
52//! * "default" - use nativetls
53//! * "default-rustls" - use rusttls
54//! * "blocking" - enable blocking api
55//! * "rustls" - enable rustls for reqwest
56//! * "nativetls" - add support for nativetls DEFAULT
57//! * "gzip" - enable gzip in reqwest
58//! * "brotli" - enable brotli in reqwest
59//! * "deflate" - enable deflate in reqwest
60
61mod api_error;
62mod create_instance_builder;
63mod data;
64mod error;
65
66use api_error::UpcloudApiErrorRoot;
67use data::{
68    UpcloudAccountRoot, UpcloudAccountsListRoot, UpcloudPlanListRoot, UpcloudPricesListRoot,
69    UpcloudServerListRoot, UpcloudServerRoot, UpcloudServerTemplateListRoot, UpcloudZoneListRoot,
70};
71use serde::Serialize;
72use serde_json::json;
73
74pub use create_instance_builder::CreateInstanceBuilder;
75pub use data::{
76    UpcloudAccount, UpcloudAccountsListItem, UpcloudLabel, UpcloudLabelList, UpcloudPlan,
77    UpcloudPrice, UpcloudPricesZone, UpcloudServer, UpcloudServerTemplate, UpcloudTagList,
78    UpcloudZone,
79};
80pub use error::UpcloudError;
81
82#[derive(Clone)]
83pub struct UpcloudApi {
84    username: String,
85    password: String,
86}
87
88impl<'a> UpcloudApi {
89    pub fn new<S1, S2>(username: S1, password: S2) -> UpcloudApi
90    where
91        S1: Into<String>,
92        S2: Into<String>,
93    {
94        UpcloudApi {
95            username: username.into(),
96            password: password.into(),
97        }
98    }
99
100    #[cfg(feature = "blocking")]
101    fn get(&self, url: &str) -> Result<reqwest::blocking::Response, UpcloudError> {
102        let client = reqwest::blocking::Client::new();
103        let resp = client
104            .get(url)
105            .basic_auth(&self.username, Some(&self.password))
106            .send()?;
107        let status = resp.status();
108        if status.is_client_error() {
109            let result: UpcloudApiErrorRoot = resp.json()?;
110            Err(UpcloudError::Api(result.error.error_message))
111        } else {
112            Ok(resp.error_for_status()?)
113        }
114    }
115
116    async fn get_async(&self, url: &str) -> Result<reqwest::Response, UpcloudError> {
117        let client = reqwest::Client::new();
118        let resp = client
119            .get(url)
120            .basic_auth(&self.username, Some(&self.password))
121            .send()
122            .await?;
123        let status = resp.status();
124        if status.is_client_error() {
125            let result: UpcloudApiErrorRoot = resp.json().await?;
126            Err(UpcloudError::Api(result.error.error_message))
127        } else {
128            Ok(resp.error_for_status()?)
129        }
130    }
131
132    #[cfg(feature = "blocking")]
133    fn post<T>(&self, url: &str, json: T) -> Result<reqwest::blocking::Response, UpcloudError>
134    where
135        T: Serialize + Sized,
136    {
137        let client = reqwest::blocking::Client::new();
138        let resp = client
139            .post(url)
140            .basic_auth(&self.username, Some(&self.password))
141            .json(&json)
142            .send()?;
143        let status = resp.status();
144        if status.is_client_error() {
145            let result: UpcloudApiErrorRoot = resp.json()?;
146            Err(UpcloudError::Api(result.error.error_message))
147        } else {
148            Ok(resp.error_for_status()?)
149        }
150    }
151
152    async fn post_async<T>(&self, url: &str, json: T) -> Result<reqwest::Response, UpcloudError>
153    where
154        T: Serialize + Sized,
155    {
156        let client = reqwest::Client::new();
157        let resp = client
158            .post(url)
159            .basic_auth(&self.username, Some(&self.password))
160            .json(&json)
161            .send()
162            .await?;
163        let status = resp.status();
164        if status.is_client_error() {
165            let result: UpcloudApiErrorRoot = resp.json().await?;
166            Err(UpcloudError::Api(result.error.error_message))
167        } else {
168            Ok(resp.error_for_status()?)
169        }
170    }
171
172    #[cfg(feature = "blocking")]
173    fn delete(&self, url: &str) -> Result<reqwest::blocking::Response, UpcloudError> {
174        let client = reqwest::blocking::Client::new();
175        let resp = client
176            .delete(url)
177            .basic_auth(&self.username, Some(&self.password))
178            .send()?;
179        let status = resp.status();
180        if status.is_client_error() {
181            let result: UpcloudApiErrorRoot = resp.json()?;
182            Err(UpcloudError::Api(result.error.error_message))
183        } else {
184            Ok(resp.error_for_status()?)
185        }
186    }
187
188    async fn delete_async(&self, url: &str) -> Result<reqwest::Response, UpcloudError> {
189        let client = reqwest::Client::new();
190        let resp = client
191            .delete(url)
192            .basic_auth(&self.username, Some(&self.password))
193            .send()
194            .await?;
195        let status = resp.status();
196        if status.is_client_error() {
197            let result: UpcloudApiErrorRoot = resp.json().await?;
198            Err(UpcloudError::Api(result.error.error_message))
199        } else {
200            Ok(resp.error_for_status()?)
201        }
202    }
203
204    #[cfg(feature = "blocking")]
205    pub fn get_account_info(&self) -> Result<UpcloudAccount, UpcloudError> {
206        Ok(self
207            .get("https://api.Upcloud.com/1.3/account")?
208            .json::<UpcloudAccountRoot>()?
209            .account)
210    }
211
212    pub async fn get_account_info_async(&self) -> Result<UpcloudAccount, UpcloudError> {
213        Ok(self
214            .get_async("https://api.Upcloud.com/1.3/account")
215            .await?
216            .json::<UpcloudAccountRoot>()
217            .await?
218            .account)
219    }
220
221    #[cfg(feature = "blocking")]
222    pub fn get_account_list(&self) -> Result<Vec<UpcloudAccountsListItem>, UpcloudError> {
223        Ok(self
224            .get("https://api.Upcloud.com/1.3/account/list")?
225            .json::<UpcloudAccountsListRoot>()?
226            .accounts
227            .account)
228    }
229
230    pub async fn get_account_list_async(
231        &self,
232    ) -> Result<Vec<UpcloudAccountsListItem>, UpcloudError> {
233        Ok(self
234            .get_async("https://api.Upcloud.com/1.3/account/list")
235            .await?
236            .json::<UpcloudAccountsListRoot>()
237            .await?
238            .accounts
239            .account)
240    }
241
242    #[cfg(feature = "blocking")]
243    pub fn get_prices(&self) -> Result<Vec<UpcloudPricesZone>, UpcloudError> {
244        Ok(self
245            .get("https://api.Upcloud.com/1.3/price")?
246            .json::<UpcloudPricesListRoot>()?
247            .prices
248            .zone)
249    }
250
251    pub async fn get_prices_async(&self) -> Result<Vec<UpcloudPricesZone>, UpcloudError> {
252        Ok(self
253            .get_async("https://api.Upcloud.com/1.3/price")
254            .await?
255            .json::<UpcloudPricesListRoot>()
256            .await?
257            .prices
258            .zone)
259    }
260
261    #[cfg(feature = "blocking")]
262    pub fn get_zones(&self) -> Result<Vec<UpcloudZone>, UpcloudError> {
263        Ok(self
264            .get("https://api.Upcloud.com/1.3/zone")?
265            .json::<UpcloudZoneListRoot>()?
266            .zones
267            .zone)
268    }
269
270    pub async fn get_zones_async(&self) -> Result<Vec<UpcloudZone>, UpcloudError> {
271        Ok(self
272            .get_async("https://api.Upcloud.com/1.3/zone")
273            .await?
274            .json::<UpcloudZoneListRoot>()
275            .await?
276            .zones
277            .zone)
278    }
279
280    #[cfg(feature = "blocking")]
281    pub fn get_plans(&self) -> Result<Vec<UpcloudPlan>, UpcloudError> {
282        Ok(self
283            .get("https://api.Upcloud.com/1.3/plan")?
284            .json::<UpcloudPlanListRoot>()?
285            .plans
286            .plan)
287    }
288
289    pub async fn get_plans_async(&self) -> Result<Vec<UpcloudPlan>, UpcloudError> {
290        Ok(self
291            .get_async("https://api.Upcloud.com/1.3/plan")
292            .await?
293            .json::<UpcloudPlanListRoot>()
294            .await?
295            .plans
296            .plan)
297    }
298
299    #[cfg(feature = "blocking")]
300    pub fn get_servers(&self) -> Result<Vec<UpcloudServer>, UpcloudError> {
301        Ok(self
302            .get("https://api.Upcloud.com/1.3/server")?
303            .json::<UpcloudServerListRoot>()?
304            .servers
305            .server)
306    }
307
308    pub async fn get_servers_async(&self) -> Result<Vec<UpcloudServer>, UpcloudError> {
309        Ok(self
310            .get_async("https://api.Upcloud.com/1.3/server")
311            .await?
312            .json::<UpcloudServerListRoot>()
313            .await?
314            .servers
315            .server)
316    }
317
318    #[cfg(feature = "blocking")]
319    pub fn get_server_details(&self, machine_id: &str) -> Result<UpcloudServer, UpcloudError> {
320        Ok(self
321            .get(&format!("https://api.Upcloud.com/1.3/server/{uuid}", uuid = machine_id))?
322            .json::<UpcloudServerRoot>()?
323            .server)
324    }
325
326    pub async fn get_server_details_async(&self, machine_id: &str) -> Result<UpcloudServer, UpcloudError> {
327        Ok(self
328            .get_async(&format!("https://api.Upcloud.com/1.3/server/{uuid}", uuid = machine_id))
329            .await?
330            .json::<UpcloudServerRoot>()
331            .await?
332            .server)
333    }
334
335    #[cfg(feature = "blocking")]
336    pub fn get_server_templates(&self) -> Result<Vec<UpcloudServerTemplate>, UpcloudError> {
337        Ok(self
338            .get("https://api.Upcloud.com/1.3/storage/template")?
339            .json::<UpcloudServerTemplateListRoot>()?
340            .storages
341            .storage)
342    }
343
344    pub async fn get_server_templates_async(
345        &self,
346    ) -> Result<Vec<UpcloudServerTemplate>, UpcloudError> {
347        Ok(self
348            .get_async("https://api.Upcloud.com/1.3/storage/template")
349            .await?
350            .json::<UpcloudServerTemplateListRoot>()
351            .await?
352            .storages
353            .storage)
354    }
355
356    /// More information at <https://developers.upcloud.com/1.3/8-servers/#create-server>
357    pub fn create_instance<S1, S2, S3, S4, S5>(
358        &self,
359        region_id: S1,
360        plan_id: S2,
361        os_id: S3,
362        title: S4,
363        hostname: S5,
364    ) -> CreateInstanceBuilder
365    where
366        S1: Into<String> + Serialize,
367        S2: Into<String> + Serialize,
368        S3: Into<String> + Serialize,
369        S4: Into<String> + Serialize,
370        S5: Into<String> + Serialize,
371    {
372        CreateInstanceBuilder::new(self.clone(), region_id, plan_id, os_id, title, hostname)
373    }
374
375    #[cfg(feature = "blocking")]
376    pub fn delete_instance(&self, machine_uuid: &str) -> Result<(), UpcloudError> {
377        self.delete(&format!(
378            "https://api.Upcloud.com/1.3/server/{uuid}?storages=true&backups=delete",
379            uuid = machine_uuid
380        ))?;
381        Ok(())
382    }
383
384    pub async fn delete_instance_async(&self, machine_uuid: &str) -> Result<(), UpcloudError> {
385        self.delete_async(&format!(
386            "https://api.Upcloud.com/1.3/server/{uuid}?storages=true&backups=delete",
387            uuid = machine_uuid
388        ))
389        .await?;
390        Ok(())
391    }
392
393    #[cfg(feature = "blocking")]
394    pub fn stop_instance(&self, machine_uuid: &str) -> Result<UpcloudServer, UpcloudError> {
395        let server = self
396            .post(
397                &format!(
398                    "https://api.Upcloud.com/1.3/server/{uuid}/stop",
399                    uuid = machine_uuid
400                ),
401                json! ({
402                    "stop_server": {
403                        "stop_type": "hard"
404                      }
405                }),
406            )?
407            .json::<UpcloudServerRoot>()?
408            .server;
409        Ok(server)
410    }
411
412    pub async fn stop_instance_async(
413        &self,
414        machine_uuid: &str,
415    ) -> Result<UpcloudServer, UpcloudError> {
416        let server = self
417            .post_async(
418                &format!(
419                    "https://api.Upcloud.com/1.3/server/{uuid}/stop",
420                    uuid = machine_uuid
421                ),
422                json! ({
423                    "stop_server": {
424                        "stop_type": "hard"
425                      }
426                }),
427            )
428            .await?
429            .json::<UpcloudServerRoot>()
430            .await?
431            .server;
432        Ok(server)
433    }
434}