Skip to main content

server_watchdog/infrastructure/common/
api_client.rs

1use reqwest::Client;
2use serde::de::DeserializeOwned;
3use serde::Serialize;
4
5pub struct ApiClient {
6    base_url: String,
7    http: Client,
8    token: Option<String>,
9}
10
11impl ApiClient {
12    pub fn new(base_url: impl Into<String>) -> Self {
13        Self {
14            base_url: base_url.into(),
15            http: Client::new(),
16            token: None,
17        }
18    }
19
20    pub fn with_token(mut self, token: String) -> Self {
21        self.token = Some(token);
22        self
23    }
24
25    fn build_url(&self,
26                 path: &str,
27                 params: Option<&[(&str, &str)]>
28    ) -> String {
29        let mut url = format!(
30            "{}/{}",
31            self.base_url.trim_end_matches('/'),
32            path.trim_start_matches('/')
33        );
34
35        if let Some(params) = params {
36            if !params.is_empty() {
37                url.push('?');
38                let query = params.iter()
39                    .map(|(k, v)| format!("{}={}", k, v))
40                    .collect::<Vec<_>>()
41                    .join("&");
42                url.push_str(&query);
43            }
44        }
45
46        url
47    }
48
49    fn apply_headers<'a>(
50        &'a self,
51        mut req: reqwest::RequestBuilder,
52        extra_headers: Option<&[(&str, &str)]>
53    ) -> reqwest::RequestBuilder {
54        if let Some(token) = &self.token {
55            req = req.bearer_auth(token);
56        }
57
58        if let Some(headers) = extra_headers {
59            for (k, v) in headers {
60                req = req.header(*k, *v);
61            }
62        }
63
64        req
65    }
66
67    // -----------------------
68    //        GET JSON
69    // -----------------------
70    pub async fn get_json<TResp>(
71        &self,
72        path: &str,
73        extra_headers: Option<&[(&str, &str)]>,
74        params: Option<&[(&str, &str)]>,
75    ) -> Result<TResp, Box<dyn std::error::Error + Send + Sync>>
76    where
77        TResp: DeserializeOwned,
78    {
79        let url = self.build_url(path, params);
80        let req = self.http.get(&url);
81        let req = self.apply_headers(req, extra_headers);
82
83        let resp = req.send().await?.error_for_status()?;
84        Ok(resp.json::<TResp>().await?)
85    }
86
87    // -----------------------
88    //   POST application/json
89    // -----------------------
90    pub async fn post_json<TBody, TResp>(
91        &self,
92        path: &str,
93        body: &TBody,
94        extra_headers: Option<&[(&str, &str)]>,
95        params: Option<&[(&str, &str)]>,
96    ) -> Result<TResp, Box<dyn std::error::Error + Send + Sync>>
97    where
98        TBody: Serialize + ?Sized,
99        TResp: DeserializeOwned,
100    {
101        let url = self.build_url(path, params);
102        let req = self.http.post(&url).json(body);
103        let req = self.apply_headers(req, extra_headers);
104
105        let resp = req.send().await?.error_for_status()?;
106        Ok(resp.json::<TResp>().await?)
107    }
108
109    // --------------------------------------
110    //   POST application/x-www-form-urlencoded
111    // --------------------------------------
112    pub async fn post_form<TForm, TResp>(
113        &self,
114        path: &str,
115        form: &TForm,
116        extra_headers: Option<&[(&str, &str)]>,
117        params: Option<&[(&str, &str)]>,
118    ) -> Result<TResp, Box<dyn std::error::Error + Send + Sync>>
119    where
120        TForm: Serialize + ?Sized,
121        TResp: DeserializeOwned,
122    {
123        let url = self.build_url(path, params);
124        let req = self.http.post(&url).form(form);
125        let req = self.apply_headers(req, extra_headers);
126
127        let resp = req.send().await?.error_for_status()?;
128        Ok(resp.json::<TResp>().await?)
129    }
130
131    // -----------------------
132    //   PUT application/json
133    // -----------------------
134    pub async fn put_json<TBody, TResp>(
135        &self,
136        path: &str,
137        body: &TBody,
138        extra_headers: Option<&[(&str, &str)]>,
139        params: Option<&[(&str, &str)]>,
140    ) -> Result<TResp, Box<dyn std::error::Error + Send + Sync>>
141    where
142        TBody: Serialize + ?Sized,
143        TResp: DeserializeOwned,
144    {
145        let url = self.build_url(path, params);
146        let req = self.http.put(&url).json(body);
147        let req = self.apply_headers(req, extra_headers);
148
149        let resp = req.send().await?.error_for_status()?;
150        Ok(resp.json::<TResp>().await?)
151    }
152
153    // --------------------------------------
154    //   PUT application/x-www-form-urlencoded
155    // --------------------------------------
156    pub async fn put_form<TForm, TResp>(
157        &self,
158        path: &str,
159        form: &TForm,
160        extra_headers: Option<&[(&str, &str)]>,
161        params: Option<&[(&str, &str)]>,
162    ) -> Result<TResp, Box<dyn std::error::Error + Send + Sync>>
163    where
164        TForm: Serialize + ?Sized,
165        TResp: DeserializeOwned,
166    {
167        let url = self.build_url(path, params);
168        let req = self.http.put(&url).form(form);
169        let req = self.apply_headers(req, extra_headers);
170
171        let resp = req.send().await?.error_for_status()?;
172        Ok(resp.json::<TResp>().await?)
173    }
174
175    // -----------------------
176    //         DELETE
177    // -----------------------
178    pub async fn delete_json<TResp>(
179        &self,
180        path: &str,
181        extra_headers: Option<&[(&str, &str)]>,
182        params: Option<&[(&str, &str)]>,
183    ) -> Result<TResp, Box<dyn std::error::Error + Send + Sync>>
184    where
185        TResp: DeserializeOwned,
186    {
187        let url = self.build_url(path, params);
188        let req = self.http.delete(&url);
189        let req = self.apply_headers(req, extra_headers);
190
191        let resp = req.send().await?.error_for_status()?;
192        Ok(resp.json::<TResp>().await?)
193    }
194}