nylas_types/
draft.rs

1//! Draft types for the Nylas API v3.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{Attachment, DraftId, EmailAddress, FolderId, GrantId, ThreadId};
6
7/// A draft message object from the Nylas API.
8///
9/// Drafts are email messages that are being composed but not yet sent.
10///
11/// # Example
12///
13/// ```
14/// # use nylas_types::{Draft, DraftId, GrantId, EmailAddress};
15/// let draft = Draft {
16///     id: DraftId::new("draft_123"),
17///     grant_id: GrantId::new("grant_123"),
18///     thread_id: None,
19///     subject: Some("Draft Subject".to_string()),
20///     from: vec![],
21///     to: vec![],
22///     cc: vec![],
23///     bcc: vec![],
24///     reply_to: vec![],
25///     body: Some("Draft body".to_string()),
26///     starred: Some(false),
27///     snippet: Some("Draft snippet...".to_string()),
28///     attachments: vec![],
29///     folders: vec![],
30///     created_at: Some(1234567890),
31/// };
32/// ```
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct Draft {
35    /// Unique identifier for the draft.
36    pub id: DraftId,
37
38    /// Grant ID associated with this draft.
39    pub grant_id: GrantId,
40
41    /// Thread ID this draft belongs to (for replies).
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub thread_id: Option<ThreadId>,
44
45    /// Subject line of the draft.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub subject: Option<String>,
48
49    /// Sender email address(es).
50    #[serde(default)]
51    pub from: Vec<EmailAddress>,
52
53    /// Recipient email address(es).
54    #[serde(default)]
55    pub to: Vec<EmailAddress>,
56
57    /// CC recipient email address(es).
58    #[serde(default)]
59    pub cc: Vec<EmailAddress>,
60
61    /// BCC recipient email address(es).
62    #[serde(default)]
63    pub bcc: Vec<EmailAddress>,
64
65    /// Reply-to email address(es).
66    #[serde(default)]
67    pub reply_to: Vec<EmailAddress>,
68
69    /// Body of the draft.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub body: Option<String>,
72
73    /// Whether the draft is starred.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub starred: Option<bool>,
76
77    /// Short snippet of the draft body.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub snippet: Option<String>,
80
81    /// Attachments included with the draft.
82    #[serde(default)]
83    pub attachments: Vec<Attachment>,
84
85    /// Folder IDs this draft belongs to.
86    #[serde(default)]
87    pub folders: Vec<FolderId>,
88
89    /// Unix timestamp when the draft was created.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub created_at: Option<i64>,
92}
93
94/// Request to create a new draft.
95///
96/// # Example
97///
98/// ```
99/// # use nylas_types::{CreateDraftRequest, EmailAddress};
100/// let to = vec![EmailAddress::new("recipient@example.com").unwrap()];
101/// let draft = CreateDraftRequest::builder()
102///     .to(to)
103///     .subject("Hello")
104///     .body("Draft message")
105///     .build();
106/// ```
107#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
108pub struct CreateDraftRequest {
109    /// Subject line of the draft.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub subject: Option<String>,
112
113    /// Sender email address(es).
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub from: Option<Vec<EmailAddress>>,
116
117    /// Recipient email address(es).
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub to: Option<Vec<EmailAddress>>,
120
121    /// CC recipient email address(es).
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub cc: Option<Vec<EmailAddress>>,
124
125    /// BCC recipient email address(es).
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub bcc: Option<Vec<EmailAddress>>,
128
129    /// Reply-to email address(es).
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub reply_to: Option<Vec<EmailAddress>>,
132
133    /// Body of the draft.
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub body: Option<String>,
136
137    /// Thread ID for reply drafts.
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub thread_id: Option<String>,
140
141    /// Whether the draft is starred.
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub starred: Option<bool>,
144}
145
146impl CreateDraftRequest {
147    /// Create a new builder for draft creation.
148    pub fn builder() -> CreateDraftRequestBuilder {
149        CreateDraftRequestBuilder::default()
150    }
151}
152
153/// Builder for create draft request.
154#[derive(Debug, Clone, Default)]
155pub struct CreateDraftRequestBuilder {
156    request: CreateDraftRequest,
157}
158
159impl CreateDraftRequestBuilder {
160    /// Set the subject line.
161    pub fn subject(mut self, subject: impl Into<String>) -> Self {
162        self.request.subject = Some(subject.into());
163        self
164    }
165
166    /// Set sender email address(es).
167    pub fn from(mut self, from: Vec<EmailAddress>) -> Self {
168        self.request.from = Some(from);
169        self
170    }
171
172    /// Set recipient email address(es).
173    pub fn to(mut self, to: Vec<EmailAddress>) -> Self {
174        self.request.to = Some(to);
175        self
176    }
177
178    /// Set CC recipient email address(es).
179    pub fn cc(mut self, cc: Vec<EmailAddress>) -> Self {
180        self.request.cc = Some(cc);
181        self
182    }
183
184    /// Set BCC recipient email address(es).
185    pub fn bcc(mut self, bcc: Vec<EmailAddress>) -> Self {
186        self.request.bcc = Some(bcc);
187        self
188    }
189
190    /// Set reply-to email address(es).
191    pub fn reply_to(mut self, reply_to: Vec<EmailAddress>) -> Self {
192        self.request.reply_to = Some(reply_to);
193        self
194    }
195
196    /// Set the body content.
197    pub fn body(mut self, body: impl Into<String>) -> Self {
198        self.request.body = Some(body.into());
199        self
200    }
201
202    /// Set thread ID for reply drafts.
203    pub fn thread_id(mut self, thread_id: impl Into<String>) -> Self {
204        self.request.thread_id = Some(thread_id.into());
205        self
206    }
207
208    /// Set starred status.
209    pub fn starred(mut self, starred: bool) -> Self {
210        self.request.starred = Some(starred);
211        self
212    }
213
214    /// Build the create draft request.
215    pub fn build(self) -> CreateDraftRequest {
216        self.request
217    }
218}
219
220/// Request to update an existing draft.
221///
222/// # Example
223///
224/// ```
225/// # use nylas_types::UpdateDraftRequest;
226/// let update = UpdateDraftRequest::builder()
227///     .subject("Updated subject")
228///     .body("Updated body")
229///     .build();
230/// ```
231#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
232pub struct UpdateDraftRequest {
233    /// Subject line of the draft.
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub subject: Option<String>,
236
237    /// Sender email address(es).
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub from: Option<Vec<EmailAddress>>,
240
241    /// Recipient email address(es).
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub to: Option<Vec<EmailAddress>>,
244
245    /// CC recipient email address(es).
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub cc: Option<Vec<EmailAddress>>,
248
249    /// BCC recipient email address(es).
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub bcc: Option<Vec<EmailAddress>>,
252
253    /// Reply-to email address(es).
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub reply_to: Option<Vec<EmailAddress>>,
256
257    /// Body of the draft.
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub body: Option<String>,
260
261    /// Whether the draft is starred.
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub starred: Option<bool>,
264}
265
266impl UpdateDraftRequest {
267    /// Create a new builder for draft update.
268    pub fn builder() -> UpdateDraftRequestBuilder {
269        UpdateDraftRequestBuilder::default()
270    }
271}
272
273/// Builder for update draft request.
274#[derive(Debug, Clone, Default)]
275pub struct UpdateDraftRequestBuilder {
276    request: UpdateDraftRequest,
277}
278
279impl UpdateDraftRequestBuilder {
280    /// Update the subject line.
281    pub fn subject(mut self, subject: impl Into<String>) -> Self {
282        self.request.subject = Some(subject.into());
283        self
284    }
285
286    /// Update sender email address(es).
287    pub fn from(mut self, from: Vec<EmailAddress>) -> Self {
288        self.request.from = Some(from);
289        self
290    }
291
292    /// Update recipient email address(es).
293    pub fn to(mut self, to: Vec<EmailAddress>) -> Self {
294        self.request.to = Some(to);
295        self
296    }
297
298    /// Update CC recipient email address(es).
299    pub fn cc(mut self, cc: Vec<EmailAddress>) -> Self {
300        self.request.cc = Some(cc);
301        self
302    }
303
304    /// Update BCC recipient email address(es).
305    pub fn bcc(mut self, bcc: Vec<EmailAddress>) -> Self {
306        self.request.bcc = Some(bcc);
307        self
308    }
309
310    /// Update reply-to email address(es).
311    pub fn reply_to(mut self, reply_to: Vec<EmailAddress>) -> Self {
312        self.request.reply_to = Some(reply_to);
313        self
314    }
315
316    /// Update the body content.
317    pub fn body(mut self, body: impl Into<String>) -> Self {
318        self.request.body = Some(body.into());
319        self
320    }
321
322    /// Update starred status.
323    pub fn starred(mut self, starred: bool) -> Self {
324        self.request.starred = Some(starred);
325        self
326    }
327
328    /// Build the update draft request.
329    pub fn build(self) -> UpdateDraftRequest {
330        self.request
331    }
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    #[test]
339    fn test_draft_creation() {
340        let draft = Draft {
341            id: DraftId::new("draft_123"),
342            grant_id: GrantId::new("grant_123"),
343            thread_id: Some(ThreadId::new("thread_123")),
344            subject: Some("Test Draft".to_string()),
345            from: vec![],
346            to: vec![],
347            cc: vec![],
348            bcc: vec![],
349            reply_to: vec![],
350            body: Some("Draft body".to_string()),
351            starred: Some(false),
352            snippet: Some("Draft snippet".to_string()),
353            attachments: vec![],
354            folders: vec![],
355            created_at: Some(1234567890),
356        };
357
358        assert_eq!(draft.id.as_str(), "draft_123");
359        assert_eq!(draft.grant_id.as_str(), "grant_123");
360        assert_eq!(draft.subject, Some("Test Draft".to_string()));
361    }
362
363    #[test]
364    fn test_draft_serialization() {
365        let draft = Draft {
366            id: DraftId::new("draft_123"),
367            grant_id: GrantId::new("grant_123"),
368            thread_id: None,
369            subject: Some("Test".to_string()),
370            from: vec![],
371            to: vec![],
372            cc: vec![],
373            bcc: vec![],
374            reply_to: vec![],
375            body: Some("Body".to_string()),
376            starred: Some(false),
377            snippet: Some("Snippet".to_string()),
378            attachments: vec![],
379            folders: vec![],
380            created_at: Some(1234567890),
381        };
382
383        let json = serde_json::to_string(&draft).unwrap();
384        assert!(json.contains("draft_123"));
385        assert!(json.contains("grant_123"));
386        assert!(json.contains("Test"));
387
388        let deserialized: Draft = serde_json::from_str(&json).unwrap();
389        assert_eq!(deserialized, draft);
390    }
391
392    #[test]
393    fn test_create_draft_request_builder() {
394        let to = vec![EmailAddress::new("recipient@example.com").unwrap()];
395        let draft = CreateDraftRequest::builder()
396            .subject("Test")
397            .to(to.clone())
398            .body("Test body")
399            .build();
400
401        assert_eq!(draft.subject, Some("Test".to_string()));
402        assert_eq!(draft.to, Some(to));
403        assert_eq!(draft.body, Some("Test body".to_string()));
404    }
405
406    #[test]
407    fn test_create_draft_request_all_fields() {
408        let from = vec![EmailAddress::new("sender@example.com").unwrap()];
409        let to = vec![EmailAddress::new("recipient@example.com").unwrap()];
410        let cc = vec![EmailAddress::new("cc@example.com").unwrap()];
411
412        let draft = CreateDraftRequest::builder()
413            .subject("Subject")
414            .from(from.clone())
415            .to(to.clone())
416            .cc(cc.clone())
417            .body("Body content")
418            .thread_id("thread_123")
419            .starred(true)
420            .build();
421
422        assert_eq!(draft.subject, Some("Subject".to_string()));
423        assert_eq!(draft.from, Some(from));
424        assert_eq!(draft.to, Some(to));
425        assert_eq!(draft.cc, Some(cc));
426        assert_eq!(draft.body, Some("Body content".to_string()));
427        assert_eq!(draft.thread_id, Some("thread_123".to_string()));
428        assert_eq!(draft.starred, Some(true));
429    }
430
431    #[test]
432    fn test_create_draft_request_serialization() {
433        let to = vec![EmailAddress::new("recipient@example.com").unwrap()];
434        let draft = CreateDraftRequest::builder()
435            .subject("Test")
436            .to(to)
437            .body("Body")
438            .build();
439
440        let json = serde_json::to_string(&draft).unwrap();
441        let deserialized: CreateDraftRequest = serde_json::from_str(&json).unwrap();
442        assert_eq!(deserialized, draft);
443    }
444
445    #[test]
446    fn test_update_draft_request_builder() {
447        let update = UpdateDraftRequest::builder()
448            .subject("Updated subject")
449            .body("Updated body")
450            .starred(true)
451            .build();
452
453        assert_eq!(update.subject, Some("Updated subject".to_string()));
454        assert_eq!(update.body, Some("Updated body".to_string()));
455        assert_eq!(update.starred, Some(true));
456    }
457
458    #[test]
459    fn test_update_draft_request_with_recipients() {
460        let to = vec![EmailAddress::new("new@example.com").unwrap()];
461        let update = UpdateDraftRequest::builder()
462            .to(to.clone())
463            .subject("New subject")
464            .build();
465
466        assert_eq!(update.to, Some(to));
467        assert_eq!(update.subject, Some("New subject".to_string()));
468    }
469
470    #[test]
471    fn test_update_draft_request_serialization() {
472        let update = UpdateDraftRequest::builder()
473            .subject("Test")
474            .body("Body")
475            .build();
476
477        let json = serde_json::to_string(&update).unwrap();
478        let deserialized: UpdateDraftRequest = serde_json::from_str(&json).unwrap();
479        assert_eq!(deserialized, update);
480    }
481}