gotify_rs/
async_gotify.rs

1use std::collections::HashMap;
2use std::str::FromStr;
3
4use anyhow::Result;
5use reqwest::Method;
6use serde::de::DeserializeOwned;
7
8use async_trait::async_trait;
9use reqwest::Client as AsyncClient;
10
11use crate::response_types::*;
12use crate::Gotify;
13
14/// Asynchronous interface to the Gotify API.
15pub struct AsyncGotify<'a> {
16    gotify: Gotify<'a>,
17    client: AsyncClient,
18}
19
20impl<'a> AsyncGotify<'a> {
21    pub fn new(
22        base_url: &'a str,
23        app_token: Option<&'a str>,
24        client_token: Option<&'a str>,
25    ) -> Self {
26        let gotify = Gotify::new(base_url, app_token, client_token);
27        let client = AsyncClient::new();
28        Self { gotify, client }
29    }
30    pub fn from(gotify: Gotify<'a>) -> Self {
31        let client = AsyncClient::new();
32        Self { gotify, client }
33    }
34
35    fn get_token(&self, auth_mode: Option<&str>) -> Option<&'a str> {
36        if let Some(mode) = auth_mode {
37            if mode == "app" {
38                self.gotify.app_token
39            } else {
40                self.gotify.client_token
41            }
42        } else {
43            self.gotify.app_token
44        }
45    }
46}
47
48#[async_trait]
49trait AsyncGotifyImpl {
50    async fn do_request<T: DeserializeOwned>(
51        &self,
52        method: &str,
53        endpoint_url: &str,
54        data: Option<HashMap<String, Option<String>>>,
55        file: Option<tokio::fs::File>,
56        auth_mode: Option<&str>,
57    ) -> Result<T>;
58
59    async fn applications(&self) -> Result<Vec<Application>>;
60
61    async fn create_application(&self, name: String, description: String) -> Result<Application>;
62
63    async fn update_application(
64        &self,
65        id: i32,
66        name: String,
67        description: Option<String>,
68    ) -> Result<Application>;
69
70    async fn delete_application(&self, id: i32) -> Result<()>;
71
72    async fn upload_application_image(
73        &self,
74        id: i32,
75        image: tokio::fs::File,
76    ) -> Result<Application>;
77
78    async fn get_messages(
79        &self,
80        app_id: Option<i32>,
81        limit: Option<i32>,
82        since: Option<i32>,
83    ) -> Result<PagedMessages>;
84
85    async fn create_message(
86        &self,
87        message: String,
88        priority: Option<i32>,
89        title: Option<String>,
90        // TODO: extras
91    ) -> Result<Message>;
92
93    async fn delete_messages(&self, app_id: Option<i32>) -> Result<()>;
94
95    async fn delete_message(&self, msg_id: i32) -> Result<()>;
96
97    async fn get_clients(&self) -> Result<Vec<Client>>;
98
99    async fn create_client(&self, name: String) -> Result<Client>;
100
101    async fn update_client(&self, id: i32, name: String) -> Result<Client>;
102
103    async fn delete_client(&self, id: i32) -> Result<()>;
104
105    async fn get_current_user(&self) -> Result<User>;
106
107    async fn set_password(&self, passwd: String) -> Result<()>;
108
109    async fn get_users(&self) -> Result<Vec<User>>;
110
111    async fn create_user(&self, name: String, passwd: String, admin: Option<bool>) -> Result<User>;
112
113    async fn get_user(&self, id: i32) -> Result<User>;
114
115    async fn update_user(
116        &self,
117        id: i32,
118        name: Option<String>,
119        passwd: Option<String>,
120        admin: Option<bool>,
121    ) -> Result<User>;
122
123    async fn delete_user(&self, id: i32) -> Result<()>;
124
125    async fn get_health(&self) -> Result<Health>;
126
127    async fn get_plugins(&self) -> Result<Vec<PluginConf>>;
128
129    async fn get_plugin_config(&self, id: i32) -> Result<PluginConf>;
130
131    /// TODO(ethanhs): Figure out what this looks like
132    /* async fn update_plugin_config(&self, id: i32); */
133
134    async fn disable_plugin(&self, id: i32) -> Result<()>;
135
136    async fn get_plugin_display(&self, id: i32) -> Result<String>;
137
138    async fn enable_plugin(&self, id: i32) -> Result<()>;
139
140    async fn get_version(&self) -> Result<VersionInfo>;
141}
142
143#[async_trait]
144impl<'a> AsyncGotifyImpl for AsyncGotify<'a> {
145    async fn do_request<T: DeserializeOwned>(
146        &self,
147        method: &str,
148        endpoint_url: &str,
149        data: Option<HashMap<String, Option<String>>>,
150        file: Option<tokio::fs::File>,
151        auth_mode: Option<&str>,
152    ) -> Result<T> {
153        let method = Method::from_str(&method)?;
154        let request_url = format!("{}/{}", self.gotify.base_url, endpoint_url);
155        let mut request = self.client.request(method.clone(), request_url);
156        if let Some(f) = file {
157            request = request.body(f);
158        } else {
159            if let Some(data) = data {
160                match method {
161                    Method::GET => {
162                        request = request.query(&data);
163                    }
164                    _ => {
165                        request = request.json(&data);
166                    }
167                }
168                request = request.json(&data);
169            }
170        }
171        let token = self.get_token(auth_mode).expect("missing token");
172        request = request.header("X-Gotify-Key", token);
173        let response = request.send().await?;
174        Ok(response.json::<T>().await?)
175    }
176
177    async fn applications(&self) -> Result<Vec<Application>> {
178        self.do_request("get", "/applications", None, None, None)
179            .await
180    }
181
182    async fn create_application(&self, name: String, description: String) -> Result<Application> {
183        let mut data = HashMap::new();
184        data.insert("name".to_owned(), Some(name));
185        data.insert("description".to_owned(), Some(description));
186        self.do_request("post", "/application", Some(data), None, None)
187            .await
188    }
189
190    async fn update_application(
191        &self,
192        id: i32,
193        name: String,
194        description: Option<String>,
195    ) -> Result<Application> {
196        let mut data = HashMap::new();
197        data.insert("name".to_string(), Some(name));
198        data.insert("description".to_string(), description);
199        self.do_request("put", &format!("/application/{id}"), Some(data), None, None)
200            .await
201    }
202
203    async fn delete_application(&self, id: i32) -> Result<()> {
204        self.do_request("delete", &format!("/application/{id}"), None, None, None)
205            .await
206    }
207
208    async fn upload_application_image(
209        &self,
210        id: i32,
211        image: tokio::fs::File,
212    ) -> Result<Application> {
213        self.do_request(
214            "post",
215            &format!("/application/{id}/image"),
216            None,
217            Some(image),
218            None,
219        )
220        .await
221    }
222
223    async fn get_messages(
224        &self,
225        app_id: Option<i32>,
226        limit: Option<i32>,
227        since: Option<i32>,
228    ) -> Result<PagedMessages> {
229        let mut data = HashMap::new();
230        data.insert("limit".to_string(), limit.map(|i| i.to_string()));
231        data.insert("since".to_string(), since.map(|i| i.to_string()));
232        if let Some(id) = app_id {
233            self.do_request(
234                "get",
235                &format!("/application/{id}/message"),
236                Some(data),
237                None,
238                None,
239            )
240            .await
241        } else {
242            self.do_request("get", "/message", Some(data), None, None)
243                .await
244        }
245    }
246
247    async fn create_message(
248        &self,
249        message: String,
250        priority: Option<i32>,
251        title: Option<String>,
252        // TODO: extras
253    ) -> Result<Message> {
254        let mut data = HashMap::new();
255        data.insert("message".to_string(), Some(message));
256        data.insert("priority".to_string(), priority.map(|i| i.to_string()));
257        data.insert("title".to_string(), title);
258        self.do_request("post", "/message", Some(data), None, Some("app"))
259            .await
260    }
261
262    async fn delete_messages(&self, app_id: Option<i32>) -> Result<()> {
263        if let Some(id) = app_id {
264            self.do_request(
265                "delete",
266                &format!("/application/{id}/message"),
267                None,
268                None,
269                None,
270            )
271            .await
272        } else {
273            self.do_request("delete", &format!("/message"), None, None, None)
274                .await
275        }
276    }
277
278    async fn delete_message(&self, msg_id: i32) -> Result<()> {
279        self.do_request("delete", &format!("/message/{msg_id}"), None, None, None)
280            .await
281    }
282
283    async fn get_clients(&self) -> Result<Vec<Client>> {
284        self.do_request("get", "/client", None, None, None).await
285    }
286
287    async fn create_client(&self, name: String) -> Result<Client> {
288        let mut data = HashMap::new();
289        data.insert("name".to_string(), Some(name));
290        self.do_request("post", "/client", Some(data), None, None)
291            .await
292    }
293
294    async fn update_client(&self, id: i32, name: String) -> Result<Client> {
295        let mut data = HashMap::new();
296        data.insert("name".to_string(), Some(name));
297        self.do_request("put", &format!("/client/{id}"), Some(data), None, None)
298            .await
299    }
300
301    async fn delete_client(&self, id: i32) -> Result<()> {
302        self.do_request("delete", &format!("/client/{id}"), None, None, None)
303            .await
304    }
305
306    async fn get_current_user(&self) -> Result<User> {
307        self.do_request("get", "/current/user", None, None, None)
308            .await
309    }
310
311    async fn set_password(&self, passwd: String) -> Result<()> {
312        let mut data = HashMap::new();
313        data.insert("pass".to_string(), Some(passwd));
314        self.do_request("get", "/current/user/password", Some(data), None, None)
315            .await
316    }
317
318    async fn get_users(&self) -> Result<Vec<User>> {
319        self.do_request("get", "/user", None, None, None).await
320    }
321
322    async fn create_user(&self, name: String, passwd: String, admin: Option<bool>) -> Result<User> {
323        let mut data = HashMap::new();
324        data.insert("pass".to_string(), Some(passwd));
325        data.insert("name".to_string(), Some(name));
326        data.insert(
327            "admin".to_string(),
328            Some(if admin.unwrap_or(false) {
329                "true".to_string()
330            } else {
331                "false".to_string()
332            }),
333        );
334        self.do_request("post", "/user", Some(data), None, None)
335            .await
336    }
337
338    async fn get_user(&self, id: i32) -> Result<User> {
339        self.do_request("get", &format!("/user/{id}"), None, None, None)
340            .await
341    }
342
343    async fn update_user(
344        &self,
345        id: i32,
346        name: Option<String>,
347        passwd: Option<String>,
348        admin: Option<bool>,
349    ) -> Result<User> {
350        let mut data = HashMap::new();
351        data.insert("pass".to_string(), passwd);
352        data.insert("name".to_string(), name);
353        data.insert(
354            "admin".to_string(),
355            Some(if admin.unwrap_or(false) {
356                "true".to_string()
357            } else {
358                "false".to_string()
359            }),
360        );
361        self.do_request("put", &format!("/user/{id}"), Some(data), None, None)
362            .await
363    }
364
365    async fn delete_user(&self, id: i32) -> Result<()> {
366        self.do_request("delete", &format!("/user/{id}"), None, None, None)
367            .await
368    }
369
370    async fn get_health(&self) -> Result<Health> {
371        self.do_request("get", "/health", None, None, None).await
372    }
373
374    async fn get_plugins(&self) -> Result<Vec<PluginConf>> {
375        self.do_request("get", "/plugins", None, None, None).await
376    }
377
378    async fn get_plugin_config(&self, id: i32) -> Result<PluginConf> {
379        self.do_request("get", &format!("/plugins/{id}/config"), None, None, None)
380            .await
381    }
382
383    /// TODO(ethanhs): Figure out what this looks like
384    /* async fn update_plugin_config(&self, id: i32) {
385        unimplemented!()
386    } */
387
388    async fn disable_plugin(&self, id: i32) -> Result<()> {
389        self.do_request("post", &format!("/plugins/{id}/disable"), None, None, None)
390            .await
391    }
392
393    async fn get_plugin_display(&self, id: i32) -> Result<String> {
394        self.do_request("get", &format!("/plugins/{id}/display"), None, None, None)
395            .await
396    }
397
398    async fn enable_plugin(&self, id: i32) -> Result<()> {
399        self.do_request("post", &format!("/plugins/{id}/enable"), None, None, None)
400            .await
401    }
402
403    async fn get_version(&self) -> Result<VersionInfo> {
404        self.do_request("get", "/version", None, None, None).await
405    }
406}