pageseeder_api/
lib.rs

1pub mod model;
2pub mod oauth;
3pub mod services;
4#[cfg(test)]
5mod tests;
6
7use std::sync::Mutex;
8
9use chrono::Utc;
10use reqwest::header::{HeaderMap, HeaderValue};
11use reqwest::{Body, Client, Response};
12use serde::Serialize;
13
14use model::{PSError, PSResult};
15
16use self::oauth::PSToken;
17
18#[derive(Debug)]
19/// A struct for making asynchronous calls to a PageSeeder server.
20pub struct PSServer {
21    pub url: String,
22    pub credentials: oauth::PSCredentials,
23    pub token: Mutex<Option<PSToken>>,
24    client: Client,
25}
26
27impl PSServer {
28    /// Instantiates a new PSServer.
29    /// Defaults to HTTPS and port 443.
30    pub fn new(url: String, credentials: oauth::PSCredentials) -> Self {
31        PSServer {
32            url,
33            credentials,
34            token: Mutex::new(None),
35            client: Client::new(),
36        }
37    }
38
39    pub fn preauth(url: String, credentials: oauth::PSCredentials, token: PSToken) -> Self {
40        PSServer {
41            url,
42            credentials,
43            token: Mutex::new(Some(token)),
44            client: Client::new(),
45        }
46    }
47
48    /// Returns the uri slug appended to the PS url.
49    fn format_url(&self, uri: &str) -> String {
50        format!("{}/{}", self.url, uri.trim_start_matches('/'))
51    }
52
53    // Unchecked
54
55    /// Makes a get request to the server at the specified uri slug.
56    async fn get<U: Into<String>>(
57        &self,
58        uri: U,
59        params: Option<Vec<(&str, &str)>>,
60        headers: Option<HeaderMap<HeaderValue>>,
61    ) -> PSResult<Response> {
62        let uri = uri.into();
63        let mut req = self.client.get(self.format_url(&uri));
64
65        if let Some(params) = params {
66            req = req.query(&params);
67        }
68        if let Some(headers) = headers {
69            req = req.headers(headers)
70        }
71
72        Ok(req.send().await?)
73    }
74
75    /// Makes a post request to the server at the specified uri slug.
76    /// Body data is included if provided.
77    async fn post<U: Into<String>, T: Into<Body>>(
78        &self,
79        uri: U,
80        params: Option<Vec<(&str, &str)>>,
81        headers: Option<HeaderMap<HeaderValue>>,
82        body: Option<T>,
83    ) -> PSResult<Response> {
84        let uri = uri.into();
85        let mut req = self.client.post(self.format_url(&uri));
86
87        if let Some(params) = params {
88            req = req.query(&params);
89        }
90        if let Some(headers) = headers {
91            req = req.headers(headers);
92        }
93        if let Some(body) = body {
94            req = req.body(body);
95        }
96
97        Ok(req.send().await?)
98    }
99
100    /// Makes a post request to the server at the specified uri slug.
101    /// Form data is included if provided.
102    async fn _post_form<F: Serialize + ?Sized>(
103        &self,
104        uri_slug: &str,
105        params: Option<Vec<(&str, &str)>>,
106        headers: Option<HeaderMap<HeaderValue>>,
107        form: Option<&F>,
108    ) -> PSResult<Response> {
109        let mut req = self.client.post(self.format_url(uri_slug));
110
111        if let Some(params) = params {
112            req = req.query(&params);
113        }
114        if let Some(headers) = headers {
115            req = req.headers(headers);
116        }
117        if let Some(form) = form {
118            req = req.form(form);
119        }
120
121        Ok(req.send().await?)
122    }
123
124    async fn put<U: Into<String>, T: Into<Body>>(
125        &self,
126        uri: U,
127        params: Option<Vec<(&str, &str)>>,
128        headers: Option<HeaderMap<HeaderValue>>,
129        body: Option<T>,
130    ) -> PSResult<Response> {
131        let uri = uri.into();
132        let mut req = self.client.put(self.format_url(&uri));
133
134        if let Some(params) = params {
135            req = req.query(&params);
136        }
137        if let Some(headers) = headers {
138            req = req.headers(headers);
139        }
140        if let Some(body) = body {
141            req = req.body(body);
142        }
143
144        Ok(req.send().await?)
145    }
146
147    // Token
148
149    /// Returns true if the currently stored token is valid.
150    fn valid_token(&self) -> bool {
151        if let Some(token) = (*self.token.lock().unwrap()).as_ref() {
152            token.expiry.gt(&Utc::now())
153        } else {
154            false
155        }
156    }
157
158    /// Gets a new access token for the server.
159    async fn get_token(&self) -> PSResult<PSToken> {
160        let resp = self
161            .client
162            .post(self.format_url("/ps/oauth/token"))
163            .form(&self.credentials.to_map())
164            .send()
165            .await?;
166
167        let resp_text = match resp.text().await {
168            Err(err) => {
169                return Err(PSError::TokenError {
170                    msg: format!("Failed to get text from token response: {:?}", err),
171                })
172            }
173            Ok(txt) => txt,
174        };
175
176        let token_resp: oauth::TokenResponse = match serde_json::from_str(&resp_text) {
177            Err(err) => {
178                return Err(PSError::TokenError {
179                    msg: format!(
180                        "Failed to parse response as json: {:?}. Response was: {}",
181                        err, &resp_text
182                    ),
183                })
184            }
185            Ok(tr) => tr,
186        };
187        PSToken::expires_in(token_resp.access_token, token_resp.expires_in)
188    }
189
190    /// Gets a new access token and stores it only if the current one is invalid.
191    pub async fn update_token(&self) -> PSResult<HeaderValue> {
192        let header = if !self.valid_token() {
193            let new_token = self.get_token().await?;
194            self.token.lock().unwrap().insert(new_token).header.clone()
195        } else {
196            self.token.lock().unwrap().as_ref().unwrap().header.clone()
197        };
198        Ok(header)
199    }
200
201    // Checked
202
203    /// Makes a get request.
204    /// Gets a new oauth token if necessary.
205    pub async fn checked_get<U: Into<String>>(
206        &self,
207        uri: U,
208        params: Option<Vec<(&str, &str)>>,
209        headers: Option<HeaderMap<HeaderValue>>,
210    ) -> PSResult<Response> {
211        let token = self.update_token().await?;
212        let mut new_headers = headers.unwrap_or_default();
213        new_headers.insert("authorization", token.clone());
214        self.get(uri, params, Some(new_headers)).await
215    }
216
217    async fn checked_post<U: Into<String>, T: Into<Body>>(
218        &self,
219        uri: U,
220        params: Option<Vec<(&str, &str)>>,
221        headers: Option<HeaderMap<HeaderValue>>,
222        body: Option<T>,
223    ) -> PSResult<Response> {
224        let token = self.update_token().await?;
225        let mut new_headers = headers.unwrap_or_default();
226        new_headers.insert("authorization", token.clone());
227        self.post(uri, params, Some(new_headers), body).await
228    }
229
230    async fn _checked_post_form<F: Serialize + ?Sized>(
231        &self,
232        uri_slug: &str,
233        params: Option<Vec<(&str, &str)>>,
234        headers: Option<HeaderMap<HeaderValue>>,
235        form: Option<&F>,
236    ) -> PSResult<Response> {
237        let token = self.update_token().await?;
238        let mut new_headers = headers.unwrap_or_default();
239        new_headers.insert("authorization", token.clone());
240        self._post_form(uri_slug, params, Some(new_headers), form)
241            .await
242    }
243
244    async fn checked_put<U: Into<String>, T: Into<Body>>(
245        &self,
246        uri: U,
247        params: Option<Vec<(&str, &str)>>,
248        headers: Option<HeaderMap<HeaderValue>>,
249        body: Option<T>,
250    ) -> PSResult<Response> {
251        let token = self.update_token().await?;
252        let mut new_headers = headers.unwrap_or_default();
253        new_headers.insert("authorization", token.clone());
254        self.put(uri, params, Some(new_headers), body).await
255    }
256}