nylas-types 0.1.1

Type definitions for Nylas API v3
Documentation
//! Folder types for Nylas API v3
//!
//! Folders represent email folders (IMAP) or labels (Gmail).
//! In v3, labels and folders are unified into a single Folder resource.

use serde::{Deserialize, Serialize};

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

/// A folder or label.
///
/// Represents email folders (IMAP) or labels (Gmail).
/// In Nylas API v3, these are unified into a single resource.
///
/// # Example
///
/// ```
/// # use nylas_types::{Folder, FolderId, GrantId};
/// let folder = Folder {
///     id: FolderId::new("folder_123"),
///     grant_id: GrantId::new("grant_456"),
///     name: "Important".to_string(),
///     parent_id: None,
///     child_count: Some(0),
///     unread_count: Some(5),
///     total_count: Some(42),
///     system_folder: Some(false),
///     background_color: None,
///     text_color: None,
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Folder {
    /// Unique identifier for the folder.
    pub id: FolderId,

    /// Grant ID this folder belongs to.
    pub grant_id: GrantId,

    /// Name of the folder.
    pub name: String,

    /// Parent folder ID (for nested folders).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub parent_id: Option<String>,

    /// Number of child folders.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub child_count: Option<i32>,

    /// Number of unread messages in this folder.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub unread_count: Option<i32>,

    /// Total number of messages in this folder.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub total_count: Option<i32>,

    /// Whether this is a system folder (Inbox, Sent, Trash, etc.).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub system_folder: Option<bool>,

    /// Background color (Gmail labels only).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub background_color: Option<String>,

    /// Text color (Gmail labels only).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub text_color: Option<String>,
}

/// Request to create a new folder.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CreateFolderRequest {
    /// Name of the folder.
    pub name: String,

    /// Parent folder ID (for nested folders).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub parent_id: Option<String>,

    /// Background color (Gmail only).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub background_color: Option<String>,

    /// Text color (Gmail only).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub text_color: Option<String>,
}

impl CreateFolderRequest {
    /// Create a builder for CreateFolderRequest.
    pub fn builder(name: impl Into<String>) -> CreateFolderRequestBuilder {
        CreateFolderRequestBuilder::new(name)
    }
}

/// Builder for CreateFolderRequest.
#[derive(Debug, Clone)]
pub struct CreateFolderRequestBuilder {
    name: String,
    parent_id: Option<String>,
    background_color: Option<String>,
    text_color: Option<String>,
}

impl CreateFolderRequestBuilder {
    /// Create a new builder.
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            parent_id: None,
            background_color: None,
            text_color: None,
        }
    }

    /// Set parent folder ID.
    pub fn parent_id(mut self, parent_id: impl Into<String>) -> Self {
        self.parent_id = Some(parent_id.into());
        self
    }

    /// Set background color (Gmail only).
    pub fn background_color(mut self, color: impl Into<String>) -> Self {
        self.background_color = Some(color.into());
        self
    }

    /// Set text color (Gmail only).
    pub fn text_color(mut self, color: impl Into<String>) -> Self {
        self.text_color = Some(color.into());
        self
    }

    /// Build the CreateFolderRequest.
    pub fn build(self) -> CreateFolderRequest {
        CreateFolderRequest {
            name: self.name,
            parent_id: self.parent_id,
            background_color: self.background_color,
            text_color: self.text_color,
        }
    }
}

/// Request to update a folder.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct UpdateFolderRequest {
    /// Update folder name.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,

    /// Update parent folder ID.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub parent_id: Option<String>,

    /// Update background color (Gmail only).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub background_color: Option<String>,

    /// Update text color (Gmail only).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub text_color: Option<String>,
}

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

/// Builder for UpdateFolderRequest.
#[derive(Debug, Clone, Default)]
pub struct UpdateFolderRequestBuilder {
    name: Option<String>,
    parent_id: Option<String>,
    background_color: Option<String>,
    text_color: Option<String>,
}

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

    /// Set parent folder ID.
    pub fn parent_id(mut self, parent_id: impl Into<String>) -> Self {
        self.parent_id = Some(parent_id.into());
        self
    }

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

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

    /// Build the UpdateFolderRequest.
    pub fn build(self) -> UpdateFolderRequest {
        UpdateFolderRequest {
            name: self.name,
            parent_id: self.parent_id,
            background_color: self.background_color,
            text_color: self.text_color,
        }
    }
}

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

    #[test]
    fn test_folder_creation() {
        let folder = Folder {
            id: FolderId::new("folder_123"),
            grant_id: GrantId::new("grant_456"),
            name: "Important".to_string(),
            parent_id: None,
            child_count: Some(0),
            unread_count: Some(5),
            total_count: Some(42),
            system_folder: Some(false),
            background_color: None,
            text_color: None,
        };

        assert_eq!(folder.id.as_str(), "folder_123");
        assert_eq!(folder.name, "Important");
        assert_eq!(folder.unread_count, Some(5));
    }

    #[test]
    fn test_folder_serialization() {
        let folder = Folder {
            id: FolderId::new("folder_123"),
            grant_id: GrantId::new("grant_456"),
            name: "Work".to_string(),
            parent_id: None,
            child_count: None,
            unread_count: Some(10),
            total_count: Some(100),
            system_folder: Some(false),
            background_color: Some("#ff0000".to_string()),
            text_color: Some("#ffffff".to_string()),
        };

        let json = serde_json::to_string(&folder).unwrap();
        assert!(json.contains("folder_123"));
        assert!(json.contains("Work"));
    }

    #[test]
    fn test_folder_deserialization() {
        let json = r#"{
            "id": "folder_123",
            "grant_id": "grant_456",
            "name": "Archive",
            "unread_count": 0,
            "total_count": 500,
            "system_folder": false
        }"#;

        let folder: Folder = serde_json::from_str(json).unwrap();
        assert_eq!(folder.id.as_str(), "folder_123");
        assert_eq!(folder.name, "Archive");
        assert_eq!(folder.unread_count, Some(0));
    }

    #[test]
    fn test_create_folder_request_builder() {
        let request = CreateFolderRequest::builder("Projects")
            .parent_id("folder_parent")
            .background_color("#ff0000")
            .text_color("#ffffff")
            .build();

        assert_eq!(request.name, "Projects");
        assert_eq!(request.parent_id, Some("folder_parent".to_string()));
        assert_eq!(request.background_color, Some("#ff0000".to_string()));
        assert_eq!(request.text_color, Some("#ffffff".to_string()));
    }

    #[test]
    fn test_create_folder_request_minimal() {
        let request = CreateFolderRequest::builder("Simple Folder").build();

        assert_eq!(request.name, "Simple Folder");
        assert_eq!(request.parent_id, None);
        assert_eq!(request.background_color, None);
    }

    #[test]
    fn test_update_folder_request_builder() {
        let update = UpdateFolderRequest::builder()
            .name("Renamed Folder")
            .background_color("#00ff00")
            .build();

        assert_eq!(update.name, Some("Renamed Folder".to_string()));
        assert_eq!(update.background_color, Some("#00ff00".to_string()));
        assert_eq!(update.parent_id, None);
    }

    #[test]
    fn test_update_folder_request_partial() {
        let update = UpdateFolderRequest::builder().name("New Name").build();

        assert_eq!(update.name, Some("New Name".to_string()));
        assert_eq!(update.parent_id, None);
        assert_eq!(update.background_color, None);
    }
}