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(¶ms)
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}