Skip to main content

nylas_types/
attachment.rs

1//! Attachment types for the Nylas API v3.
2
3use serde::{Deserialize, Serialize};
4
5/// An attachment object from the Nylas API.
6///
7/// Attachments are files attached to messages or drafts.
8///
9/// # Example
10///
11/// ```
12/// # use nylas_types::Attachment;
13/// let attachment = Attachment {
14///     id: "attachment_123".to_string(),
15///     grant_id: Some("grant_123".to_string()),
16///     filename: Some("document.pdf".to_string()),
17///     content_type: Some("application/pdf".to_string()),
18///     size: Some(12345),
19///     content_id: None,
20///     is_inline: Some(false),
21/// };
22/// ```
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct Attachment {
25    /// Unique identifier for the attachment.
26    pub id: String,
27
28    /// Grant ID associated with this attachment.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub grant_id: Option<String>,
31
32    /// Filename of the attachment.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub filename: Option<String>,
35
36    /// MIME type of the attachment.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub content_type: Option<String>,
39
40    /// Size of the attachment in bytes.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub size: Option<u64>,
43
44    /// Content ID for inline attachments (used in HTML emails).
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub content_id: Option<String>,
47
48    /// Whether the attachment is displayed inline.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub is_inline: Option<bool>,
51}
52
53impl Attachment {
54    /// Create a new attachment builder.
55    pub fn builder() -> AttachmentBuilder {
56        AttachmentBuilder::default()
57    }
58}
59
60/// Builder for creating attachments.
61#[derive(Debug, Clone, Default)]
62pub struct AttachmentBuilder {
63    id: Option<String>,
64    grant_id: Option<String>,
65    filename: Option<String>,
66    content_type: Option<String>,
67    size: Option<u64>,
68    content_id: Option<String>,
69    is_inline: Option<bool>,
70}
71
72impl AttachmentBuilder {
73    /// Set the attachment ID.
74    pub fn id(mut self, id: impl Into<String>) -> Self {
75        self.id = Some(id.into());
76        self
77    }
78
79    /// Set the grant ID.
80    pub fn grant_id(mut self, grant_id: impl Into<String>) -> Self {
81        self.grant_id = Some(grant_id.into());
82        self
83    }
84
85    /// Set the filename.
86    pub fn filename(mut self, filename: impl Into<String>) -> Self {
87        self.filename = Some(filename.into());
88        self
89    }
90
91    /// Set the content type.
92    pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
93        self.content_type = Some(content_type.into());
94        self
95    }
96
97    /// Set the size in bytes.
98    pub fn size(mut self, size: u64) -> Self {
99        self.size = Some(size);
100        self
101    }
102
103    /// Set the content ID for inline attachments.
104    pub fn content_id(mut self, content_id: impl Into<String>) -> Self {
105        self.content_id = Some(content_id.into());
106        self
107    }
108
109    /// Set whether the attachment is inline.
110    pub fn is_inline(mut self, is_inline: bool) -> Self {
111        self.is_inline = Some(is_inline);
112        self
113    }
114
115    /// Build the attachment.
116    ///
117    /// # Panics
118    ///
119    /// Panics if the ID is not set.
120    pub fn build(self) -> Attachment {
121        Attachment {
122            id: self.id.expect("Attachment ID is required"),
123            grant_id: self.grant_id,
124            filename: self.filename,
125            content_type: self.content_type,
126            size: self.size,
127            content_id: self.content_id,
128            is_inline: self.is_inline,
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_attachment_creation() {
139        let attachment = Attachment {
140            id: "att_123".to_string(),
141            grant_id: Some("grant_123".to_string()),
142            filename: Some("document.pdf".to_string()),
143            content_type: Some("application/pdf".to_string()),
144            size: Some(12345),
145            content_id: None,
146            is_inline: Some(false),
147        };
148
149        assert_eq!(attachment.id, "att_123");
150        assert_eq!(attachment.filename, Some("document.pdf".to_string()));
151        assert_eq!(attachment.size, Some(12345));
152    }
153
154    #[test]
155    fn test_attachment_builder() {
156        let attachment = Attachment::builder()
157            .id("att_123")
158            .filename("test.pdf")
159            .content_type("application/pdf")
160            .size(1024)
161            .is_inline(false)
162            .build();
163
164        assert_eq!(attachment.id, "att_123");
165        assert_eq!(attachment.filename, Some("test.pdf".to_string()));
166        assert_eq!(attachment.content_type, Some("application/pdf".to_string()));
167        assert_eq!(attachment.size, Some(1024));
168        assert_eq!(attachment.is_inline, Some(false));
169    }
170
171    #[test]
172    fn test_attachment_inline() {
173        let attachment = Attachment::builder()
174            .id("att_inline")
175            .filename("image.png")
176            .content_type("image/png")
177            .content_id("img001")
178            .is_inline(true)
179            .build();
180
181        assert_eq!(attachment.content_id, Some("img001".to_string()));
182        assert_eq!(attachment.is_inline, Some(true));
183    }
184
185    #[test]
186    fn test_attachment_serialization() {
187        let attachment = Attachment {
188            id: "att_123".to_string(),
189            grant_id: Some("grant_123".to_string()),
190            filename: Some("test.pdf".to_string()),
191            content_type: Some("application/pdf".to_string()),
192            size: Some(5000),
193            content_id: None,
194            is_inline: Some(false),
195        };
196
197        let json = serde_json::to_string(&attachment).unwrap();
198        assert!(json.contains("att_123"));
199        assert!(json.contains("test.pdf"));
200        assert!(json.contains("5000"));
201
202        let deserialized: Attachment = serde_json::from_str(&json).unwrap();
203        assert_eq!(deserialized, attachment);
204    }
205
206    #[test]
207    fn test_attachment_minimal() {
208        let attachment = Attachment::builder().id("att_minimal").build();
209
210        assert_eq!(attachment.id, "att_minimal");
211        assert_eq!(attachment.filename, None);
212        assert_eq!(attachment.size, None);
213    }
214
215    #[test]
216    #[should_panic(expected = "Attachment ID is required")]
217    fn test_attachment_builder_without_id() {
218        Attachment::builder().filename("test.pdf").build();
219    }
220}