Skip to main content

nylas_types/
folder.rs

1//! Folder types for Nylas API v3
2//!
3//! Folders represent email folders (IMAP) or labels (Gmail).
4//! In v3, labels and folders are unified into a single Folder resource.
5
6use serde::{Deserialize, Serialize};
7
8use crate::common::{FolderId, GrantId};
9
10/// A folder or label.
11///
12/// Represents email folders (IMAP) or labels (Gmail).
13/// In Nylas API v3, these are unified into a single resource.
14///
15/// # Example
16///
17/// ```
18/// # use nylas_types::{Folder, FolderId, GrantId};
19/// let folder = Folder {
20///     id: FolderId::new("folder_123"),
21///     grant_id: GrantId::new("grant_456"),
22///     name: "Important".to_string(),
23///     parent_id: None,
24///     child_count: Some(0),
25///     unread_count: Some(5),
26///     total_count: Some(42),
27///     system_folder: Some(false),
28///     background_color: None,
29///     text_color: None,
30/// };
31/// ```
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct Folder {
34    /// Unique identifier for the folder.
35    pub id: FolderId,
36
37    /// Grant ID this folder belongs to.
38    pub grant_id: GrantId,
39
40    /// Name of the folder.
41    pub name: String,
42
43    /// Parent folder ID (for nested folders).
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub parent_id: Option<String>,
46
47    /// Number of child folders.
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub child_count: Option<i32>,
50
51    /// Number of unread messages in this folder.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub unread_count: Option<i32>,
54
55    /// Total number of messages in this folder.
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub total_count: Option<i32>,
58
59    /// Whether this is a system folder (Inbox, Sent, Trash, etc.).
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub system_folder: Option<bool>,
62
63    /// Background color (Gmail labels only).
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub background_color: Option<String>,
66
67    /// Text color (Gmail labels only).
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub text_color: Option<String>,
70}
71
72/// Request to create a new folder.
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct CreateFolderRequest {
75    /// Name of the folder.
76    pub name: String,
77
78    /// Parent folder ID (for nested folders).
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub parent_id: Option<String>,
81
82    /// Background color (Gmail only).
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub background_color: Option<String>,
85
86    /// Text color (Gmail only).
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub text_color: Option<String>,
89}
90
91impl CreateFolderRequest {
92    /// Create a builder for CreateFolderRequest.
93    pub fn builder(name: impl Into<String>) -> CreateFolderRequestBuilder {
94        CreateFolderRequestBuilder::new(name)
95    }
96}
97
98/// Builder for CreateFolderRequest.
99#[derive(Debug, Clone)]
100pub struct CreateFolderRequestBuilder {
101    name: String,
102    parent_id: Option<String>,
103    background_color: Option<String>,
104    text_color: Option<String>,
105}
106
107impl CreateFolderRequestBuilder {
108    /// Create a new builder.
109    pub fn new(name: impl Into<String>) -> Self {
110        Self {
111            name: name.into(),
112            parent_id: None,
113            background_color: None,
114            text_color: None,
115        }
116    }
117
118    /// Set parent folder ID.
119    pub fn parent_id(mut self, parent_id: impl Into<String>) -> Self {
120        self.parent_id = Some(parent_id.into());
121        self
122    }
123
124    /// Set background color (Gmail only).
125    pub fn background_color(mut self, color: impl Into<String>) -> Self {
126        self.background_color = Some(color.into());
127        self
128    }
129
130    /// Set text color (Gmail only).
131    pub fn text_color(mut self, color: impl Into<String>) -> Self {
132        self.text_color = Some(color.into());
133        self
134    }
135
136    /// Build the CreateFolderRequest.
137    pub fn build(self) -> CreateFolderRequest {
138        CreateFolderRequest {
139            name: self.name,
140            parent_id: self.parent_id,
141            background_color: self.background_color,
142            text_color: self.text_color,
143        }
144    }
145}
146
147/// Request to update a folder.
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
149pub struct UpdateFolderRequest {
150    /// Update folder name.
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub name: Option<String>,
153
154    /// Update parent folder ID.
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub parent_id: Option<String>,
157
158    /// Update background color (Gmail only).
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub background_color: Option<String>,
161
162    /// Update text color (Gmail only).
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub text_color: Option<String>,
165}
166
167impl UpdateFolderRequest {
168    /// Create a builder for UpdateFolderRequest.
169    pub fn builder() -> UpdateFolderRequestBuilder {
170        UpdateFolderRequestBuilder::default()
171    }
172}
173
174/// Builder for UpdateFolderRequest.
175#[derive(Debug, Clone, Default)]
176pub struct UpdateFolderRequestBuilder {
177    name: Option<String>,
178    parent_id: Option<String>,
179    background_color: Option<String>,
180    text_color: Option<String>,
181}
182
183impl UpdateFolderRequestBuilder {
184    /// Set folder name.
185    pub fn name(mut self, name: impl Into<String>) -> Self {
186        self.name = Some(name.into());
187        self
188    }
189
190    /// Set parent folder ID.
191    pub fn parent_id(mut self, parent_id: impl Into<String>) -> Self {
192        self.parent_id = Some(parent_id.into());
193        self
194    }
195
196    /// Set background color.
197    pub fn background_color(mut self, color: impl Into<String>) -> Self {
198        self.background_color = Some(color.into());
199        self
200    }
201
202    /// Set text color.
203    pub fn text_color(mut self, color: impl Into<String>) -> Self {
204        self.text_color = Some(color.into());
205        self
206    }
207
208    /// Build the UpdateFolderRequest.
209    pub fn build(self) -> UpdateFolderRequest {
210        UpdateFolderRequest {
211            name: self.name,
212            parent_id: self.parent_id,
213            background_color: self.background_color,
214            text_color: self.text_color,
215        }
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_folder_creation() {
225        let folder = Folder {
226            id: FolderId::new("folder_123"),
227            grant_id: GrantId::new("grant_456"),
228            name: "Important".to_string(),
229            parent_id: None,
230            child_count: Some(0),
231            unread_count: Some(5),
232            total_count: Some(42),
233            system_folder: Some(false),
234            background_color: None,
235            text_color: None,
236        };
237
238        assert_eq!(folder.id.as_str(), "folder_123");
239        assert_eq!(folder.name, "Important");
240        assert_eq!(folder.unread_count, Some(5));
241    }
242
243    #[test]
244    fn test_folder_serialization() {
245        let folder = Folder {
246            id: FolderId::new("folder_123"),
247            grant_id: GrantId::new("grant_456"),
248            name: "Work".to_string(),
249            parent_id: None,
250            child_count: None,
251            unread_count: Some(10),
252            total_count: Some(100),
253            system_folder: Some(false),
254            background_color: Some("#ff0000".to_string()),
255            text_color: Some("#ffffff".to_string()),
256        };
257
258        let json = serde_json::to_string(&folder).unwrap();
259        assert!(json.contains("folder_123"));
260        assert!(json.contains("Work"));
261    }
262
263    #[test]
264    fn test_folder_deserialization() {
265        let json = r#"{
266            "id": "folder_123",
267            "grant_id": "grant_456",
268            "name": "Archive",
269            "unread_count": 0,
270            "total_count": 500,
271            "system_folder": false
272        }"#;
273
274        let folder: Folder = serde_json::from_str(json).unwrap();
275        assert_eq!(folder.id.as_str(), "folder_123");
276        assert_eq!(folder.name, "Archive");
277        assert_eq!(folder.unread_count, Some(0));
278    }
279
280    #[test]
281    fn test_create_folder_request_builder() {
282        let request = CreateFolderRequest::builder("Projects")
283            .parent_id("folder_parent")
284            .background_color("#ff0000")
285            .text_color("#ffffff")
286            .build();
287
288        assert_eq!(request.name, "Projects");
289        assert_eq!(request.parent_id, Some("folder_parent".to_string()));
290        assert_eq!(request.background_color, Some("#ff0000".to_string()));
291        assert_eq!(request.text_color, Some("#ffffff".to_string()));
292    }
293
294    #[test]
295    fn test_create_folder_request_minimal() {
296        let request = CreateFolderRequest::builder("Simple Folder").build();
297
298        assert_eq!(request.name, "Simple Folder");
299        assert_eq!(request.parent_id, None);
300        assert_eq!(request.background_color, None);
301    }
302
303    #[test]
304    fn test_update_folder_request_builder() {
305        let update = UpdateFolderRequest::builder()
306            .name("Renamed Folder")
307            .background_color("#00ff00")
308            .build();
309
310        assert_eq!(update.name, Some("Renamed Folder".to_string()));
311        assert_eq!(update.background_color, Some("#00ff00".to_string()));
312        assert_eq!(update.parent_id, None);
313    }
314
315    #[test]
316    fn test_update_folder_request_partial() {
317        let update = UpdateFolderRequest::builder().name("New Name").build();
318
319        assert_eq!(update.name, Some("New Name".to_string()));
320        assert_eq!(update.parent_id, None);
321        assert_eq!(update.background_color, None);
322    }
323}