Skip to main content

nylas_types/
grant.rs

1//! Grant types for Nylas API v3.
2//!
3//! Grants represent authenticated connections to email and calendar providers.
4
5use serde::{Deserialize, Serialize};
6
7use crate::common::{GrantId, Provider};
8
9/// Custom authentication settings for different providers
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(untagged)]
12pub enum CustomAuthSettings {
13    /// Google OAuth settings
14    Google {
15        /// Google OAuth refresh token
16        refresh_token: String,
17
18        /// Google client ID
19        #[serde(skip_serializing_if = "Option::is_none")]
20        client_id: Option<String>,
21
22        /// Google client secret
23        #[serde(skip_serializing_if = "Option::is_none")]
24        client_secret: Option<String>,
25    },
26
27    /// Microsoft OAuth settings
28    Microsoft {
29        /// Microsoft OAuth refresh token
30        refresh_token: String,
31
32        /// Microsoft client ID
33        #[serde(skip_serializing_if = "Option::is_none")]
34        client_id: Option<String>,
35
36        /// Microsoft client secret
37        #[serde(skip_serializing_if = "Option::is_none")]
38        client_secret: Option<String>,
39    },
40
41    /// IMAP credentials
42    Imap {
43        /// IMAP host
44        imap_host: String,
45
46        /// IMAP port
47        imap_port: u16,
48
49        /// IMAP username
50        imap_username: String,
51
52        /// IMAP password
53        imap_password: String,
54
55        /// SMTP host
56        smtp_host: String,
57
58        /// SMTP port
59        smtp_port: u16,
60
61        /// SMTP username
62        #[serde(skip_serializing_if = "Option::is_none")]
63        smtp_username: Option<String>,
64
65        /// SMTP password
66        #[serde(skip_serializing_if = "Option::is_none")]
67        smtp_password: Option<String>,
68    },
69}
70
71/// Grant settings (OAuth token or custom credentials)
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
73#[serde(untagged)]
74pub enum GrantSettings {
75    /// Use access token from hosted OAuth
76    AccessToken {
77        /// Access token
78        access_token: String,
79    },
80
81    /// Use custom authentication
82    Custom(CustomAuthSettings),
83}
84
85/// Grant status.
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
87#[serde(rename_all = "lowercase")]
88pub enum GrantStatus {
89    /// Grant is valid and active.
90    Valid,
91    /// Grant is invalid or expired.
92    Invalid,
93}
94
95/// Grant model.
96///
97/// Represents a Nylas grant which provides access to a user's account.
98///
99/// # Example
100///
101/// ```
102/// # use nylas_types::{Grant, GrantId, Provider, GrantStatus};
103/// let grant = Grant {
104///     id: GrantId::new("grant_123"),
105///     provider: Provider::Google,
106///     grant_status: Some(GrantStatus::Valid),
107///     email: Some("user@example.com".to_string()),
108///     scope: Some(vec!["https://www.googleapis.com/auth/gmail.readonly".to_string()]),
109///     user_timezone: None,
110///     created_at: Some(1234567890),
111///     updated_at: Some(1234567890),
112///     provider_user_id: None,
113///     ip: None,
114///     state: None,
115///     user_agent: None,
116///     settings: None,
117///     metadata: None,
118/// };
119/// ```
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub struct Grant {
122    /// Unique identifier for the grant.
123    pub id: GrantId,
124
125    /// Provider for this grant.
126    pub provider: Provider,
127
128    /// Grant status.
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub grant_status: Option<GrantStatus>,
131
132    /// Email address associated with this grant.
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub email: Option<String>,
135
136    /// OAuth scopes granted to this connection.
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub scope: Option<Vec<String>>,
139
140    /// User's timezone.
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub user_timezone: Option<String>,
143
144    /// Created timestamp (Unix time).
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub created_at: Option<i64>,
147
148    /// Updated timestamp (Unix time).
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub updated_at: Option<i64>,
151
152    /// Provider-specific user identifier.
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub provider_user_id: Option<String>,
155
156    /// IP address from the authentication request.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub ip: Option<String>,
159
160    /// OAuth state parameter.
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub state: Option<String>,
163
164    /// User agent from the authentication request.
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub user_agent: Option<String>,
167
168    /// Provider-specific settings.
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub settings: Option<serde_json::Value>,
171
172    /// Custom metadata (key-value pairs).
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub metadata: Option<serde_json::Value>,
175}
176
177/// Request to create a new grant.
178///
179/// Grants can be created from OAuth tokens or custom authentication credentials.
180///
181/// # Example
182///
183/// ```
184/// # use nylas_types::{CreateGrantRequest, Provider, GrantSettings};
185/// // Create from OAuth access token
186/// let request = CreateGrantRequest::from_access_token(
187///     Provider::Google,
188///     "oauth_access_token".to_string(),
189/// );
190/// ```
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
192pub struct CreateGrantRequest {
193    /// Provider type.
194    pub provider: Provider,
195
196    /// Grant settings (OAuth or custom)
197    pub settings: GrantSettings,
198
199    /// Grant state (defaults to "valid")
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub state: Option<GrantStatus>,
202
203    /// OAuth scopes to request.
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub scope: Option<Vec<String>>,
206
207    /// Custom metadata.
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub metadata: Option<serde_json::Value>,
210}
211
212impl CreateGrantRequest {
213    /// Create a builder for CreateGrantRequest.
214    pub fn builder(provider: Provider) -> CreateGrantRequestBuilder {
215        CreateGrantRequestBuilder::new(provider)
216    }
217
218    /// Create from OAuth access token
219    ///
220    /// # Example
221    ///
222    /// ```
223    /// # use nylas_types::{CreateGrantRequest, Provider};
224    /// let request = CreateGrantRequest::from_access_token(
225    ///     Provider::Google,
226    ///     "oauth_token_123".to_string(),
227    /// );
228    /// assert_eq!(request.provider, Provider::Google);
229    /// ```
230    pub fn from_access_token(provider: Provider, access_token: String) -> Self {
231        Self {
232            provider,
233            settings: GrantSettings::AccessToken { access_token },
234            state: None,
235            scope: None,
236            metadata: None,
237        }
238    }
239
240    /// Create from custom authentication settings
241    ///
242    /// # Example
243    ///
244    /// ```
245    /// # use nylas_types::{CreateGrantRequest, Provider, CustomAuthSettings};
246    /// let custom_auth = CustomAuthSettings::Imap {
247    ///     imap_host: "imap.example.com".to_string(),
248    ///     imap_port: 993,
249    ///     imap_username: "user@example.com".to_string(),
250    ///     imap_password: "password".to_string(),
251    ///     smtp_host: "smtp.example.com".to_string(),
252    ///     smtp_port: 587,
253    ///     smtp_username: None,
254    ///     smtp_password: None,
255    /// };
256    ///
257    /// let request = CreateGrantRequest::from_custom_auth(
258    ///     Provider::Imap,
259    ///     custom_auth,
260    /// );
261    /// assert_eq!(request.provider, Provider::Imap);
262    /// ```
263    pub fn from_custom_auth(provider: Provider, settings: CustomAuthSettings) -> Self {
264        Self {
265            provider,
266            settings: GrantSettings::Custom(settings),
267            state: None,
268            scope: None,
269            metadata: None,
270        }
271    }
272}
273
274/// Builder for CreateGrantRequest.
275#[derive(Debug, Clone)]
276pub struct CreateGrantRequestBuilder {
277    provider: Provider,
278    settings: Option<GrantSettings>,
279    grant_status: Option<GrantStatus>,
280    scope: Option<Vec<String>>,
281    metadata: Option<serde_json::Value>,
282}
283
284impl CreateGrantRequestBuilder {
285    /// Create a new builder.
286    pub fn new(provider: Provider) -> Self {
287        Self {
288            provider,
289            settings: None,
290            grant_status: None,
291            scope: None,
292            metadata: None,
293        }
294    }
295
296    /// Set grant settings (OAuth token or custom auth).
297    pub fn settings(mut self, settings: GrantSettings) -> Self {
298        self.settings = Some(settings);
299        self
300    }
301
302    /// Set access token (shorthand for OAuth token).
303    pub fn access_token(mut self, token: String) -> Self {
304        self.settings = Some(GrantSettings::AccessToken {
305            access_token: token,
306        });
307        self
308    }
309
310    /// Set custom authentication settings.
311    pub fn custom_auth(mut self, auth: CustomAuthSettings) -> Self {
312        self.settings = Some(GrantSettings::Custom(auth));
313        self
314    }
315
316    /// Set grant status.
317    pub fn grant_status(mut self, status: GrantStatus) -> Self {
318        self.grant_status = Some(status);
319        self
320    }
321
322    /// Set OAuth scopes.
323    pub fn scope(mut self, scope: Vec<String>) -> Self {
324        self.scope = Some(scope);
325        self
326    }
327
328    /// Set custom metadata.
329    pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
330        self.metadata = Some(metadata);
331        self
332    }
333
334    /// Build the CreateGrantRequest.
335    ///
336    /// # Panics
337    ///
338    /// Panics if settings are not provided.
339    pub fn build(self) -> CreateGrantRequest {
340        CreateGrantRequest {
341            provider: self.provider,
342            settings: self.settings.expect("settings are required"),
343            state: self.grant_status,
344            scope: self.scope,
345            metadata: self.metadata,
346        }
347    }
348}
349
350/// Request to update a grant.
351#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
352pub struct UpdateGrantRequest {
353    /// Update grant settings.
354    #[serde(skip_serializing_if = "Option::is_none")]
355    pub settings: Option<GrantSettings>,
356
357    /// Update grant state.
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub state: Option<GrantStatus>,
360
361    /// Update OAuth scopes.
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub scope: Option<Vec<String>>,
364
365    /// Update custom metadata.
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub metadata: Option<serde_json::Value>,
368}
369
370impl UpdateGrantRequest {
371    /// Create a builder for UpdateGrantRequest.
372    pub fn builder() -> UpdateGrantRequestBuilder {
373        UpdateGrantRequestBuilder::default()
374    }
375}
376
377/// Builder for UpdateGrantRequest.
378#[derive(Debug, Clone, Default)]
379pub struct UpdateGrantRequestBuilder {
380    settings: Option<GrantSettings>,
381    state: Option<GrantStatus>,
382    scope: Option<Vec<String>>,
383    metadata: Option<serde_json::Value>,
384}
385
386impl UpdateGrantRequestBuilder {
387    /// Set grant settings.
388    pub fn settings(mut self, settings: GrantSettings) -> Self {
389        self.settings = Some(settings);
390        self
391    }
392
393    /// Set grant state.
394    pub fn state(mut self, state: GrantStatus) -> Self {
395        self.state = Some(state);
396        self
397    }
398
399    /// Set OAuth scopes.
400    pub fn scope(mut self, scope: Vec<String>) -> Self {
401        self.scope = Some(scope);
402        self
403    }
404
405    /// Set custom metadata.
406    pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
407        self.metadata = Some(metadata);
408        self
409    }
410
411    /// Build the UpdateGrantRequest.
412    pub fn build(self) -> UpdateGrantRequest {
413        UpdateGrantRequest {
414            settings: self.settings,
415            state: self.state,
416            scope: self.scope,
417            metadata: self.metadata,
418        }
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425
426    #[test]
427    fn test_grant_serialization() {
428        let grant = Grant {
429            id: GrantId::new("grant_123"),
430            provider: Provider::Google,
431            grant_status: Some(GrantStatus::Valid),
432            email: Some("user@example.com".to_string()),
433            scope: Some(vec![
434                "https://www.googleapis.com/auth/gmail.readonly".to_string()
435            ]),
436            user_timezone: None,
437            created_at: Some(1234567890),
438            updated_at: Some(1234567890),
439            provider_user_id: None,
440            ip: None,
441            state: None,
442            user_agent: None,
443            settings: None,
444            metadata: None,
445        };
446
447        let json = serde_json::to_string(&grant).unwrap();
448        assert!(json.contains("grant_123"));
449        assert!(json.contains("google"));
450        assert!(json.contains("user@example.com"));
451    }
452
453    #[test]
454    fn test_grant_deserialization() {
455        let json = r#"{
456            "id": "grant_123",
457            "provider": "google",
458            "grant_status": "valid",
459            "email": "user@example.com",
460            "scope": ["https://www.googleapis.com/auth/gmail.readonly"],
461            "created_at": 1234567890,
462            "updated_at": 1234567890
463        }"#;
464
465        let grant: Grant = serde_json::from_str(json).unwrap();
466        assert_eq!(grant.id.as_str(), "grant_123");
467        assert_eq!(grant.provider, Provider::Google);
468        assert_eq!(grant.grant_status, Some(GrantStatus::Valid));
469        assert_eq!(grant.email, Some("user@example.com".to_string()));
470    }
471
472    #[test]
473    fn test_grant_status_serialization() {
474        assert_eq!(
475            serde_json::to_string(&GrantStatus::Valid).unwrap(),
476            r#""valid""#
477        );
478        assert_eq!(
479            serde_json::to_string(&GrantStatus::Invalid).unwrap(),
480            r#""invalid""#
481        );
482    }
483
484    #[test]
485    fn test_create_grant_request_builder() {
486        let request = CreateGrantRequest::builder(Provider::Google)
487            .access_token("token_123".to_string())
488            .scope(vec![
489                "https://www.googleapis.com/auth/gmail.readonly".to_string()
490            ])
491            .grant_status(GrantStatus::Valid)
492            .build();
493
494        assert_eq!(request.provider, Provider::Google);
495        assert!(request.scope.is_some());
496        assert_eq!(request.state, Some(GrantStatus::Valid));
497        assert!(request.metadata.is_none());
498    }
499
500    #[test]
501    fn test_create_grant_request_with_metadata() {
502        let metadata = serde_json::json!({"team": "engineering"});
503        let request = CreateGrantRequest::builder(Provider::Microsoft)
504            .access_token("token_456".to_string())
505            .metadata(metadata.clone())
506            .build();
507
508        assert_eq!(request.provider, Provider::Microsoft);
509        assert_eq!(request.metadata, Some(metadata));
510    }
511
512    #[test]
513    fn test_create_grant_from_access_token() {
514        let request =
515            CreateGrantRequest::from_access_token(Provider::Google, "token_123".to_string());
516
517        assert_eq!(request.provider, Provider::Google);
518        assert!(matches!(
519            request.settings,
520            GrantSettings::AccessToken { .. }
521        ));
522    }
523
524    #[test]
525    fn test_create_grant_from_custom_auth() {
526        let custom_auth = CustomAuthSettings::Imap {
527            imap_host: "imap.example.com".to_string(),
528            imap_port: 993,
529            imap_username: "user@example.com".to_string(),
530            imap_password: "password".to_string(),
531            smtp_host: "smtp.example.com".to_string(),
532            smtp_port: 587,
533            smtp_username: None,
534            smtp_password: None,
535        };
536
537        let request = CreateGrantRequest::from_custom_auth(Provider::Imap, custom_auth);
538
539        assert_eq!(request.provider, Provider::Imap);
540        assert!(matches!(request.settings, GrantSettings::Custom(_)));
541    }
542
543    #[test]
544    fn test_update_grant_request_builder() {
545        let metadata = serde_json::json!({"updated": true});
546        let request = UpdateGrantRequest::builder()
547            .metadata(metadata.clone())
548            .scope(vec!["new_scope".to_string()])
549            .state(GrantStatus::Invalid)
550            .build();
551
552        assert_eq!(request.metadata, Some(metadata));
553        assert_eq!(request.scope, Some(vec!["new_scope".to_string()]));
554        assert_eq!(request.state, Some(GrantStatus::Invalid));
555        assert!(request.settings.is_none());
556    }
557
558    #[test]
559    fn test_update_grant_request_default() {
560        let request = UpdateGrantRequest::default();
561        assert!(request.settings.is_none());
562        assert!(request.state.is_none());
563        assert!(request.scope.is_none());
564        assert!(request.metadata.is_none());
565    }
566}