nylas-types 0.1.0

Type definitions for Nylas API v3
Documentation
//! Grant types for Nylas API v3.
//!
//! Grants represent authenticated connections to email and calendar providers.

use serde::{Deserialize, Serialize};

use crate::common::{GrantId, Provider};

/// Grant status.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GrantStatus {
    /// Grant is valid and active.
    Valid,
    /// Grant is invalid or expired.
    Invalid,
}

/// Grant model.
///
/// Represents a Nylas grant which provides access to a user's account.
///
/// # Example
///
/// ```
/// # use nylas_types::{Grant, GrantId, Provider, GrantStatus};
/// let grant = Grant {
///     id: GrantId::new("grant_123"),
///     provider: Provider::Google,
///     grant_status: Some(GrantStatus::Valid),
///     email: Some("user@example.com".to_string()),
///     scope: Some(vec!["https://www.googleapis.com/auth/gmail.readonly".to_string()]),
///     user_timezone: None,
///     created_at: Some(1234567890),
///     updated_at: Some(1234567890),
///     provider_user_id: None,
///     ip: None,
///     state: None,
///     user_agent: None,
///     settings: None,
///     metadata: None,
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Grant {
    /// Unique identifier for the grant.
    pub id: GrantId,

    /// Provider for this grant.
    pub provider: Provider,

    /// Grant status.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub grant_status: Option<GrantStatus>,

    /// Email address associated with this grant.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,

    /// OAuth scopes granted to this connection.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scope: Option<Vec<String>>,

    /// User's timezone.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_timezone: Option<String>,

    /// Created timestamp (Unix time).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created_at: Option<i64>,

    /// Updated timestamp (Unix time).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated_at: Option<i64>,

    /// Provider-specific user identifier.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub provider_user_id: Option<String>,

    /// IP address from the authentication request.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ip: Option<String>,

    /// OAuth state parameter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub state: Option<String>,

    /// User agent from the authentication request.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_agent: Option<String>,

    /// Provider-specific settings.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub settings: Option<serde_json::Value>,

    /// Custom metadata (key-value pairs).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<serde_json::Value>,
}

/// Request to create a new grant.
///
/// Note: Grant creation typically happens through OAuth flow,
/// not directly via API. This is for advanced use cases.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateGrantRequest {
    /// Provider type.
    pub provider: Provider,

    /// Provider-specific settings (e.g., IMAP credentials).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub settings: Option<serde_json::Value>,

    /// OAuth scopes to request.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scope: Option<Vec<String>>,

    /// OAuth state parameter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub state: Option<String>,

    /// Custom metadata.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<serde_json::Value>,
}

impl CreateGrantRequest {
    /// Create a builder for CreateGrantRequest.
    pub fn builder(provider: Provider) -> CreateGrantRequestBuilder {
        CreateGrantRequestBuilder::new(provider)
    }
}

/// Builder for CreateGrantRequest.
#[derive(Debug, Clone)]
pub struct CreateGrantRequestBuilder {
    provider: Provider,
    settings: Option<serde_json::Value>,
    scope: Option<Vec<String>>,
    state: Option<String>,
    metadata: Option<serde_json::Value>,
}

impl CreateGrantRequestBuilder {
    /// Create a new builder.
    pub fn new(provider: Provider) -> Self {
        Self {
            provider,
            settings: None,
            scope: None,
            state: None,
            metadata: None,
        }
    }

    /// Set provider-specific settings.
    pub fn settings(mut self, settings: serde_json::Value) -> Self {
        self.settings = Some(settings);
        self
    }

    /// Set OAuth scopes.
    pub fn scope(mut self, scope: Vec<String>) -> Self {
        self.scope = Some(scope);
        self
    }

    /// Set OAuth state parameter.
    pub fn state(mut self, state: impl Into<String>) -> Self {
        self.state = Some(state.into());
        self
    }

    /// Set custom metadata.
    pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
        self.metadata = Some(metadata);
        self
    }

    /// Build the CreateGrantRequest.
    pub fn build(self) -> CreateGrantRequest {
        CreateGrantRequest {
            provider: self.provider,
            settings: self.settings,
            scope: self.scope,
            state: self.state,
            metadata: self.metadata,
        }
    }
}

/// Request to update a grant.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct UpdateGrantRequest {
    /// Update provider-specific settings.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub settings: Option<serde_json::Value>,

    /// Update OAuth scopes.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scope: Option<Vec<String>>,

    /// Update custom metadata.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<serde_json::Value>,
}

impl UpdateGrantRequest {
    /// Create a builder for UpdateGrantRequest.
    pub fn builder() -> UpdateGrantRequestBuilder {
        UpdateGrantRequestBuilder::default()
    }
}

/// Builder for UpdateGrantRequest.
#[derive(Debug, Clone, Default)]
pub struct UpdateGrantRequestBuilder {
    settings: Option<serde_json::Value>,
    scope: Option<Vec<String>>,
    metadata: Option<serde_json::Value>,
}

impl UpdateGrantRequestBuilder {
    /// Set provider-specific settings.
    pub fn settings(mut self, settings: serde_json::Value) -> Self {
        self.settings = Some(settings);
        self
    }

    /// Set OAuth scopes.
    pub fn scope(mut self, scope: Vec<String>) -> Self {
        self.scope = Some(scope);
        self
    }

    /// Set custom metadata.
    pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
        self.metadata = Some(metadata);
        self
    }

    /// Build the UpdateGrantRequest.
    pub fn build(self) -> UpdateGrantRequest {
        UpdateGrantRequest {
            settings: self.settings,
            scope: self.scope,
            metadata: self.metadata,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_grant_serialization() {
        let grant = Grant {
            id: GrantId::new("grant_123"),
            provider: Provider::Google,
            grant_status: Some(GrantStatus::Valid),
            email: Some("user@example.com".to_string()),
            scope: Some(vec![
                "https://www.googleapis.com/auth/gmail.readonly".to_string()
            ]),
            user_timezone: None,
            created_at: Some(1234567890),
            updated_at: Some(1234567890),
            provider_user_id: None,
            ip: None,
            state: None,
            user_agent: None,
            settings: None,
            metadata: None,
        };

        let json = serde_json::to_string(&grant).unwrap();
        assert!(json.contains("grant_123"));
        assert!(json.contains("google"));
        assert!(json.contains("user@example.com"));
    }

    #[test]
    fn test_grant_deserialization() {
        let json = r#"{
            "id": "grant_123",
            "provider": "google",
            "grant_status": "valid",
            "email": "user@example.com",
            "scope": ["https://www.googleapis.com/auth/gmail.readonly"],
            "created_at": 1234567890,
            "updated_at": 1234567890
        }"#;

        let grant: Grant = serde_json::from_str(json).unwrap();
        assert_eq!(grant.id.as_str(), "grant_123");
        assert_eq!(grant.provider, Provider::Google);
        assert_eq!(grant.grant_status, Some(GrantStatus::Valid));
        assert_eq!(grant.email, Some("user@example.com".to_string()));
    }

    #[test]
    fn test_grant_status_serialization() {
        assert_eq!(
            serde_json::to_string(&GrantStatus::Valid).unwrap(),
            r#""valid""#
        );
        assert_eq!(
            serde_json::to_string(&GrantStatus::Invalid).unwrap(),
            r#""invalid""#
        );
    }

    #[test]
    fn test_create_grant_request_builder() {
        let request = CreateGrantRequest::builder(Provider::Google)
            .scope(vec![
                "https://www.googleapis.com/auth/gmail.readonly".to_string()
            ])
            .state("test_state")
            .build();

        assert_eq!(request.provider, Provider::Google);
        assert!(request.scope.is_some());
        assert_eq!(request.state, Some("test_state".to_string()));
        assert!(request.settings.is_none());
        assert!(request.metadata.is_none());
    }

    #[test]
    fn test_create_grant_request_with_metadata() {
        let metadata = serde_json::json!({"team": "engineering"});
        let request = CreateGrantRequest::builder(Provider::Microsoft)
            .metadata(metadata.clone())
            .build();

        assert_eq!(request.provider, Provider::Microsoft);
        assert_eq!(request.metadata, Some(metadata));
    }

    #[test]
    fn test_update_grant_request_builder() {
        let metadata = serde_json::json!({"updated": true});
        let request = UpdateGrantRequest::builder()
            .metadata(metadata.clone())
            .scope(vec!["new_scope".to_string()])
            .build();

        assert_eq!(request.metadata, Some(metadata));
        assert_eq!(request.scope, Some(vec!["new_scope".to_string()]));
        assert!(request.settings.is_none());
    }

    #[test]
    fn test_update_grant_request_default() {
        let request = UpdateGrantRequest::default();
        assert!(request.settings.is_none());
        assert!(request.scope.is_none());
        assert!(request.metadata.is_none());
    }
}