upcloud_sdk/
client.rs

1use reqwest::Client as ReqwestClient;
2
3use crate::constants::{API_BASE_URL, API_VERSION, VERSION};
4use crate::config::Config;
5use crate::error::Error;
6
7pub struct Client {
8    config: Config,
9    client: ReqwestClient,
10}
11
12impl Client {
13    pub fn new() -> Result<Self, Error> {
14        let config = Config::new(
15            std::env::var("UPCLOUD_USERNAME")
16                .map_err(|_| Error::ConfigError("UPCLOUD_USERNAME environment variable not set".to_string()))?,
17            std::env::var("UPCLOUD_PASSWORD")
18                .map_err(|_| Error::ConfigError("UPCLOUD_PASSWORD environment variable not set".to_string()))?
19        );
20        Self::with_config(config)
21    }
22
23    pub fn with_config(config: Config) -> Result<Self, Error> {
24        let mut client_builder = ReqwestClient::builder()
25            .user_agent(format!("upcloud-rust-sdk/{}", VERSION));
26
27        if let Some(timeout) = config.timeout {
28            client_builder = client_builder.timeout(timeout);
29        }
30
31        if let Some(hook) = config.http_client_hook.as_ref() {
32            client_builder = hook(client_builder);
33        }
34
35        let client = client_builder.build()?;
36
37        Ok(Self { config, client })
38    }
39
40    pub(crate) async fn get(&self, path: &str) -> Result<String, Error> {
41        self.request(reqwest::Method::GET, path, Option::<&()>::None).await
42    }
43
44    pub(crate) async fn post<T: serde::Serialize + std::fmt::Debug>(
45        &self,
46        path: &str,
47        body: Option<&T>,
48    ) -> Result<String, Error> {
49        println!("POST: {:?}", body);
50        self.request(reqwest::Method::POST, path, body).await
51    }
52
53    pub(crate) async fn put<T: serde::Serialize + std::fmt::Debug>(
54        &self,
55        path: &str,
56        body: Option<&T>,
57    ) -> Result<String, Error> {
58        self.request(reqwest::Method::PUT, path, body).await
59    }
60
61    pub(crate) async fn delete(&self, path: &str) -> Result<String, Error> {
62        self.request(reqwest::Method::DELETE, path, Option::<&()>::None).await
63    }
64
65    async fn request<T: serde::Serialize + std::fmt::Debug>(
66        &self,
67        method: reqwest::Method,
68        path: &str,
69        body: Option<&T>,
70    ) -> Result<String, Error> {
71        let url = format!(
72            "{}/{}/{}",
73            self.config.base_url.as_deref().unwrap_or(API_BASE_URL),
74            API_VERSION,
75            path.trim_start_matches('/')
76        );
77
78        let mut builder = self.client.request(method, &url);
79        
80        builder = builder.basic_auth(&self.config.username, Some(&self.config.password));
81
82        if let Some(body) = body {
83            builder = builder.json(body);
84        }
85
86        let response = builder.send().await?;
87        
88        if !response.status().is_success() {
89            return Err(Error::ApiError {
90                status: response.status().as_u16(),
91                message: response.text().await?,
92            });
93        }
94
95        Ok(response.text().await?)
96    }
97}