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}