nylas/
client.rs

1use crate::accounts::Account;
2use crate::messages::Messages;
3use base64::{engine::general_purpose, Engine as _};
4use std::collections::HashMap;
5use url::Url;
6
7/// The `Nylas` struct provides all methods available in the Nylas API.
8///
9/// This struct currently allows you to create authentication URLs for initiating the OAuth 2.0 flow with the Nylas API.
10///
11/// # Examples
12///
13/// To create a new `Nylas` instance with your client ID and client secret:
14///
15/// ```
16/// use nylas::client::Nylas;
17///
18/// let client_id = "YOUR_CLIENT_ID";
19/// let client_secret = "YOUR_CLIENT_SECRET";
20///
21/// let nylas = Nylas::new(client_id, client_secret, None);
22/// ```
23pub struct Nylas {
24    pub client_id: String,
25    pub client_secret: String,
26    pub account: Option<Account>,
27    pub access_token: Option<String>,
28    pub messages: Option<Messages<'static>>,
29}
30
31impl Nylas {
32    /// Create a new `Nylas` instance with the provided client ID and client secret.
33    ///
34    /// # Arguments
35    ///
36    /// * `client_id` - A string representing your Nylas API client ID.
37    /// * `client_secret` - A string representing your Nylas API client secret.
38    /// * `access_token` - An optional string representing the access token.
39    ///
40    /// # Examples
41    ///
42    /// ```
43    /// use nylas::client::Nylas;
44    ///
45    /// let client_id = "YOUR_CLIENT_ID";
46    /// let client_secret = "YOUR_CLIENT_SECRET";
47    ///
48    /// // Create a Nylas instance without an access token
49    /// let nylas = Nylas::new(client_id, client_secret, None);
50    ///
51    /// // Create a Nylas instance with an access token
52    /// let access_token = "YOUR_ACCESS_TOKEN";
53    /// let nylas_with_token = Nylas::new(client_id, client_secret, Some(access_token));
54    /// ```
55    pub async fn new(
56        client_id: &str,
57        client_secret: &str,
58        access_token: Option<&str>,
59    ) -> Result<Self, String> {
60        let mut nylas = Nylas {
61            client_id: client_id.to_string(),
62            client_secret: client_secret.to_string(),
63            access_token: access_token.map(|s| s.to_string()),
64            account: None,
65            messages: None,
66        };
67
68        if let Some(_) = nylas.access_token {
69            if let Err(error) = nylas.account().await {
70                return Err(format!("Error initializing Nylas: {}", error));
71            }
72        }
73
74        Ok(nylas)
75    }
76
77    /// Generate an authentication URL for initiating the OAuth 2.0 flow.
78    ///
79    /// The authentication URL can be opened in a web browser to allow users to grant
80    /// permission to your application.
81    ///
82    /// # Arguments
83    ///
84    /// * `redirect_uri` - The URL to which the user will be redirected after authentication.
85    /// * `login_hint` - An optional hint to pre-fill the user's email address on the authentication page.
86    /// * `state` - An optional unique identifier for the authentication request, which can be used to maintain state during the flow.
87    /// * `scopes` - An optional list of scopes that specify the permissions your application is requesting.
88    ///
89    /// # Returns
90    ///
91    /// A `Result` containing the authentication URL if successful, or an error message.
92    ///
93    /// # Errors
94    ///
95    /// Returns an error if any of the following conditions are not met:
96    /// 1. The client ID and client secret are not provided.
97    /// 2. The redirect URI is not a valid URL.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use nylas::client::Nylas;
103    ///
104    /// #[tokio::main]
105    /// async fn main() {
106    ///     let client_id = "YOUR_CLIENT_ID";
107    ///     let client_secret = "YOUR_CLIENT_SECRET";
108    ///
109    ///     let nylas = Nylas::new(client_id, client_secret, None).await.unwrap();
110    ///
111    ///     let redirect_uri = "http://example.com/login_callback";
112    ///     let login_hint = Some("your_email@example.com");
113    ///     let state = Some("unique_identifier");
114    ///     let scopes = Some("email,calendar,contacts");
115    ///
116    ///     match nylas.authentication_url(redirect_uri, login_hint, state, scopes) {
117    ///         Ok(auth_url) => println!("Authentication URL: {}", auth_url),
118    ///         Err(error) => eprintln!("Error: {}", error),
119    ///     }
120    /// }
121    /// ```
122    pub fn authentication_url(
123        &self,
124        redirect_uri: &str,
125        login_hint: Option<&str>,
126        state: Option<&str>,
127        scopes: Option<&str>,
128    ) -> Result<String, String> {
129        if self.client_id.is_empty() || self.client_secret.is_empty() {
130            return Err("Client ID and Client Secret must not be empty.".to_string());
131        }
132
133        if !Url::parse(redirect_uri).is_ok() {
134            return Err("Invalid redirect URI.".to_string());
135        }
136
137        let mut params: HashMap<&str, String> = HashMap::new();
138        params.insert("client_id", self.client_id.clone());
139        params.insert("redirect_uri", redirect_uri.to_string());
140        params.insert("response_type", "code".to_string());
141
142        if let Some(login_hint) = login_hint {
143            params.insert("login_hint", login_hint.to_string());
144        }
145
146        if let Some(state) = state {
147            params.insert("state", state.to_string());
148        }
149
150        if let Some(scopes) = scopes {
151            params.insert("scopes", scopes.to_string());
152        }
153
154        // Build the URL
155        let base_url = "https://api.nylas.com/oauth/authorize";
156        let mut url = String::from(base_url);
157        url.push('?');
158
159        for (key, value) in params.iter() {
160            url.push_str(key);
161            url.push_str("=");
162            url.push_str(value);
163            url.push('&');
164        }
165
166        // Remove the trailing '&' character
167        url.pop();
168
169        Ok(url)
170    }
171
172    /// Exchange the authorization code for an access token using hosted authentication.
173    ///
174    /// The authorization code is valid for 15 minutes and can be used only once.
175    ///
176    /// # Arguments
177    ///
178    /// * `authorization_code` - The authorization code obtained during the authentication process.
179    ///
180    /// # Returns
181    ///
182    /// A `Result` containing the access token if successful, or an error message.
183    ///
184    /// # Errors
185    ///
186    /// Returns an error if any of the following conditions are not met:
187    /// 1. The client ID and client secret are not provided.
188    /// 2. The `authorization_code` is not valid.
189    ///
190    /// # Examples
191    ///
192    /// ```
193    /// use nylas::client::Nylas;
194    ///
195    /// #[tokio::main]
196    /// async fn main() {
197    ///     let client_id = "YOUR_CLIENT_ID";
198    ///     let client_secret = "YOUR_CLIENT_SECRET";
199    ///
200    ///     let nylas = Nylas::new(client_id, client_secret, None).await.unwrap();
201    ///
202    ///     let authorization_code = "YOUR_AUTHORIZATION_CODE";
203    ///
204    ///     match nylas.exchange_access_token(authorization_code).await {
205    ///         Ok(access_token) => println!("Access Token: {}", access_token),
206    ///         Err(error) => eprintln!("Error: {}", error),
207    ///     }
208    /// }
209    /// ```
210    pub async fn exchange_access_token(&self, authorization_code: &str) -> Result<String, String> {
211        if self.client_id.is_empty() || self.client_secret.is_empty() {
212            return Err("Client ID and Client Secret must not be empty.".to_string());
213        }
214
215        let mut params: HashMap<&str, String> = HashMap::new();
216        params.insert("client_id", self.client_id.clone());
217        params.insert("client_secret", self.client_secret.clone());
218        params.insert("grant_type", "authorization_code".to_string());
219        params.insert("code", authorization_code.to_string());
220
221        // Build the URL
222        let base_url = "https://api.nylas.com/oauth/token";
223
224        // Base64 encode the client secret
225        let encoded_client_secret = general_purpose::STANDARD.encode(self.client_secret.clone());
226
227        // Make the POST request
228        let client = reqwest::Client::new();
229        let response = client
230            .post(base_url)
231            .header("Authorization", format!("Basic {}", encoded_client_secret))
232            .header("Accept", "application/json")
233            .form(&params)
234            .send()
235            .await
236            .map_err(|e| format!("Request Error: {:?}", e))?;
237
238        if response.status().is_success() {
239            let data: HashMap<String, String> = response
240                .json()
241                .await
242                .map_err(|e| format!("JSON Parsing Error: {:?}", e))?;
243            if let Some(access_token) = data.get("access_token") {
244                return Ok(access_token.to_string());
245            } else {
246                return Err("Access token not found in the response.".to_string());
247            }
248        } else {
249            // change this to return the response error message
250            let error_message = response
251                .text()
252                .await
253                .unwrap_or_else(|_| "Unknown Error".to_string());
254            return Err(format!("HTTP Error: - {}", error_message));
255        }
256    }
257
258    /// Get account details for the authenticated user and store them in the `account` member.
259    ///
260    /// # Returns
261    ///
262    /// A `Result` containing the account details if successful, or an error message.
263    ///
264    /// # Errors
265    ///
266    /// Returns an error if any of the following conditions are not met:
267    /// 1. The client ID and client secret are not provided.
268    /// 2. The access token is not valid.
269    ///
270    /// # Examples
271    ///
272    /// ```
273    /// use nylas::client::Nylas;
274    ///
275    /// #[tokio::main]
276    /// async fn main() {
277    ///     let client_id = "YOUR_CLIENT_ID";
278    ///     let client_secret = "YOUR_CLIENT_SECRET";
279    ///     let access_token = "YOUR_ACCESS_TOKEN";
280    ///
281    ///     let nylas = Nylas::new(client_id, client_secret, Some(access_token)).await.unwrap();
282    ///
283    ///     println!("Account Details: {:?}", nylas.account);
284    /// }
285    /// ```
286    pub async fn account(&mut self) -> Result<(), String> {
287        if self.client_id.is_empty() || self.client_secret.is_empty() {
288            return Err("Client ID and Client Secret must not be empty.".to_string());
289        }
290
291        if let Some(access_token) = &self.access_token {
292            // Build the URL
293            let base_url = "https://api.nylas.com/account";
294            let client = reqwest::Client::new();
295            let response = client
296                .get(base_url)
297                .header("Authorization", format!("Bearer {}", access_token))
298                .header("Accept", "application/json")
299                .send()
300                .await
301                .map_err(|e| format!("Request Error: {:?}", e))?;
302
303            if response.status().is_success() {
304                let account: Account = response
305                    .json()
306                    .await
307                    .map_err(|e| format!("JSON Parsing Error: {:?}", e))?;
308                self.account = Some(account);
309                Ok(())
310            } else {
311                Err(format!("HTTP Error: {}", response.status()))
312            }
313        } else {
314            Err("Access token must be set before calling the account method.".to_string())
315        }
316    }
317
318    /// Returns a `Messages` struct associated with this `Nylas` instance, which provides methods
319    /// for working with Nylas messages.
320    ///
321    /// # Examples
322    ///
323    /// ```
324    /// use nylas::client::Nylas;
325    ///
326    /// #[tokio::main]
327    /// async fn main() {
328    ///     let client_id = "YOUR_CLIENT_ID";
329    ///     let client_secret = "YOUR_CLIENT_SECRET";
330    ///     let access_token = "YOUR_ACCESS_TOKEN";
331    ///
332    ///     let mut nylas = Nylas::new(client_id, client_secret, Some(access_token)).await.unwrap();
333    ///
334    ///     let messages = nylas.messages();
335    /// }
336    /// ```
337    pub fn messages(&mut self) -> Messages {
338        Messages { nylas: self }
339    }
340}