gotify_rs/
lib.rs

1//! Gotify-rs is a library which wraps the Gotify API. There are two
2//! interfaces which can be used:
3//! - `SyncGotify` which is a synchronous wrapper of the Gotify API.
4//! - `AsyncGotify` which is an asynchronous wrapper of the Gotify API.
5//! 
6//! An example of using the synchronous interface to create an application:
7//! ```rust
8//! let gotify = SyncGetify::new("https://my.gotify.server/", "a_client_token", None);
9//! let app = gotify.create_application("App name".to_string(), "App description".to_string()).expect("Failed to create application");
10//! gotify.gotify.config(None, &app.token, None);
11//! let message = gotify.create_message("Hello world!".to_string(), Some(0), "Test message".to_string()).expect("Failed to create message");
12//! 
13use std::collections::HashMap;
14use std::fs::File;
15use std::str::FromStr;
16
17use reqwest::blocking::Client as SyncClient;
18use reqwest::Method;
19
20use anyhow::Result;
21use serde::de::DeserializeOwned;
22
23#[cfg(feature = "async")]
24mod async_gotify;
25mod response_types;
26#[cfg(feature = "async")]
27pub use crate::async_gotify::*;
28pub use crate::response_types::*;
29
30pub struct Gotify<'a> {
31    base_url: &'a str,
32    app_token: Option<&'a str>,
33    client_token: Option<&'a str>,
34}
35
36impl<'a> Gotify<'a> {
37    /// Create a new Gotify wrapper, which tracks the client and app tokens as well as the base URL.
38    pub fn new(base_url: &'a str, app_token: Option<&'a str>, client_token: Option<&'a str>) -> Self {
39        Self {
40            base_url: base_url.trim_end_matches("/"),
41            app_token: app_token,
42            client_token: client_token,
43        }
44    }
45
46    /// Modify the Gotify config
47    pub fn config(mut self, base_url: Option<&'a str>, app_token: Option<&'a str>, client_token: Option<&'a str>) -> Self {
48        if let Some(url) = base_url {
49            self.base_url = url.trim_end_matches("/");
50        }
51        if let Some(token) = app_token {
52            self.app_token = Some(token);
53        }
54        if let Some(token) = client_token {
55            self.client_token = Some(token);
56        }
57        self
58    }
59}
60
61/// Synchronous interface to the Gotify API.
62pub struct SyncGotify<'a> {
63    gotify: Gotify<'a>,
64    client: SyncClient,
65}
66
67impl<'a> SyncGotify<'a> {
68    pub fn new(base_url: &'a str, app_token: Option<&'a str>, client_token: Option<&'a str>) -> Self {
69        let gotify = Gotify::new(base_url, app_token, client_token);
70        let client = SyncClient::new();
71        Self { gotify, client }
72    }
73    pub fn from(gotify: Gotify<'a>) -> Self {
74        let client = SyncClient::new();
75        Self { gotify, client }
76    }
77
78    fn get_token(&self, auth_mode: Option<&str>) -> Option<&'a str> {
79        if let Some(mode) = auth_mode {
80            if mode == "app" {
81                self.gotify.app_token
82            } else {
83                self.gotify.client_token
84            }
85        } else {
86            self.gotify.app_token
87        }
88    }
89
90    fn do_request<T: DeserializeOwned>(
91        &self,
92        method: &str,
93        endpoint_url: &str,
94        data: Option<HashMap<String, Option<String>>>,
95        file: Option<File>,
96        auth_mode: Option<&str>,
97    ) -> Result<T> {
98        let method = Method::from_str(&method)?;
99        let request_url = format!("{}{}", self.gotify.base_url, endpoint_url);
100
101        let mut request = self.client.request(method.clone(), request_url);
102        if let Some(f) = file {
103            request = request.body(f);
104        } else {
105            if let Some(data) = data {
106                match method {
107                    Method::GET => {
108                        request = request.query(&data);
109                    }
110                    _ => {
111                        request = request.json(&data);
112                    }
113                }
114                request = request.json(&data);
115            }
116        }
117        let token = self.get_token(auth_mode).expect("missing token");
118        request = request.header("X-Gotify-Key", token);
119        let response = request.send()?;
120        Ok(response.json::<T>()?)
121    }
122
123    pub fn applications(&self) -> Result<Vec<Application>> {
124        self.do_request("get", "/application", None, None, None)
125    }
126
127    pub fn create_application(&self, name: String, description: String) -> Result<Application> {
128        let mut data = HashMap::new();
129        data.insert("name".to_owned(), Some(name));
130        data.insert("description".to_owned(), Some(description));
131        self.do_request("post", "/application", Some(data), None, None)
132    }
133
134    pub fn update_application(
135        &self,
136        id: i32,
137        name: String,
138        description: Option<String>,
139    ) -> Result<Application> {
140        let mut data = HashMap::new();
141        data.insert("name".to_string(), Some(name));
142        data.insert("description".to_string(), description);
143        self.do_request("put", &format!("/application/{id}"), Some(data), None, None)
144    }
145
146    pub fn delete_application(&self, id: i32) -> Result<()> {
147        self.do_request("delete", &format!("/application/{id}"), None, None, None)
148    }
149
150    pub fn upload_application_image(&self, id: i32, image: File) -> Result<Application> {
151        self.do_request(
152            "post",
153            &format!("/application/{id}/image"),
154            None,
155            Some(image),
156            None,
157        )
158    }
159
160    pub fn get_messages(
161        &self,
162        app_id: Option<i32>,
163        limit: Option<i32>,
164        since: Option<i32>,
165    ) -> Result<PagedMessages> {
166        let mut data = HashMap::new();
167        data.insert("limit".to_string(), limit.map(|i| i.to_string()));
168        data.insert("since".to_string(), since.map(|i| i.to_string()));
169        if let Some(id) = app_id {
170            self.do_request(
171                "get",
172                &format!("/application/{id}/message"),
173                Some(data),
174                None,
175                None,
176            )
177        } else {
178            self.do_request("get", "/message", Some(data), None, None)
179        }
180    }
181
182    pub fn create_message(
183        &self,
184        message: String,
185        priority: Option<i32>,
186        title: Option<String>,
187        // TODO: extras
188    ) -> Result<Message> {
189        let mut data = HashMap::new();
190        data.insert("message".to_string(), Some(message));
191        data.insert("priority".to_string(), priority.map(|i| i.to_string()));
192        data.insert("title".to_string(), title);
193        self.do_request("post", "/message", Some(data), None, Some("app"))
194    }
195
196    pub fn delete_messages(&self, app_id: Option<i32>) -> Result<()> {
197        if let Some(id) = app_id {
198            self.do_request(
199                "delete",
200                &format!("/application/{id}/message"),
201                None,
202                None,
203                None,
204            )
205        } else {
206            self.do_request("delete", &format!("/message"), None, None, None)
207        }
208    }
209
210    pub fn delete_message(&self, msg_id: i32) -> Result<()> {
211        self.do_request("delete", &format!("/message/{msg_id}"), None, None, None)
212    }
213
214    pub fn get_clients(&self) -> Result<Vec<Client>> {
215        self.do_request("get", "/client", None, None, None)
216    }
217
218    pub fn create_client(&self, name: String) -> Result<Client> {
219        let mut data = HashMap::new();
220        data.insert("name".to_string(), Some(name));
221        self.do_request("post", "/client", Some(data), None, None)
222    }
223
224    pub fn update_client(&self, id: i32, name: String) -> Result<Client> {
225        let mut data = HashMap::new();
226        data.insert("name".to_string(), Some(name));
227        self.do_request("put", &format!("/client/{id}"), Some(data), None, None)
228    }
229
230    pub fn delete_client(&self, id: i32) -> Result<()> {
231        self.do_request("delete", &format!("/client/{id}"), None, None, None)
232    }
233
234    pub fn get_current_user(&self) -> Result<User> {
235        self.do_request("get", "/current/user", None, None, None)
236    }
237
238    pub fn set_password(&self, passwd: String) -> Result<()> {
239        let mut data = HashMap::new();
240        data.insert("pass".to_string(), Some(passwd));
241        self.do_request("get", "/current/user/password", Some(data), None, None)
242    }
243
244    pub fn get_users(&self) -> Result<Vec<User>> {
245        self.do_request("get", "/user", None, None, None)
246    }
247
248    pub fn create_user(&self, name: String, passwd: String, admin: Option<bool>) -> Result<User> {
249        let mut data = HashMap::new();
250        data.insert("pass".to_string(), Some(passwd));
251        data.insert("name".to_string(), Some(name));
252        data.insert(
253            "admin".to_string(),
254            Some(if admin.unwrap_or(false) {
255                "true".to_string()
256            } else {
257                "false".to_string()
258            }),
259        );
260        self.do_request("post", "/user", Some(data), None, None)
261    }
262
263    pub fn get_user(&self, id: i32) -> Result<User> {
264        self.do_request("get", &format!("/user/{id}"), None, None, None)
265    }
266
267    pub fn update_user(
268        &self,
269        id: i32,
270        name: Option<String>,
271        passwd: Option<String>,
272        admin: Option<bool>,
273    ) -> Result<User> {
274        let mut data = HashMap::new();
275        data.insert("pass".to_string(), passwd);
276        data.insert("name".to_string(), name);
277        data.insert(
278            "admin".to_string(),
279            Some(if admin.unwrap_or(false) {
280                "true".to_string()
281            } else {
282                "false".to_string()
283            }),
284        );
285        self.do_request("put", &format!("/user/{id}"), Some(data), None, None)
286    }
287
288    pub fn delete_user(&self, id: i32) -> Result<()> {
289        self.do_request("delete", &format!("/user/{id}"), None, None, None)
290    }
291
292    pub fn get_health(&self) -> Result<Health> {
293        self.do_request("get", "/health", None, None, None)
294    }
295
296    pub fn get_plugins(&self) -> Result<Vec<PluginConf>> {
297        self.do_request("get", "/plugins", None, None, None)
298    }
299
300    pub fn get_plugin_config(&self, id: i32) -> Result<PluginConf> {
301        self.do_request("get", &format!("/plugins/{id}/config"), None, None, None)
302    }
303
304    /// TODO(ethanhs): Figure out what this looks like
305    /* pub fn update_plugin_config(&self, id: i32) {
306        unimplemented!()
307    } */
308
309    pub fn disable_plugin(&self, id: i32) -> Result<()> {
310        self.do_request("post", &format!("/plugins/{id}/disable"), None, None, None)
311    }
312
313    pub fn get_plugin_display(&self, id: i32) -> Result<String> {
314        self.do_request("get", &format!("/plugins/{id}/display"), None, None, None)
315    }
316
317    pub fn enable_plugin(&self, id: i32) -> Result<()> {
318        self.do_request("post", &format!("/plugins/{id}/enable"), None, None, None)
319    }
320
321    pub fn get_version(&self) -> Result<VersionInfo> {
322        self.do_request("get", "/version", None, None, None)
323    }
324}