Skip to main content

nylas_types/
calendar.rs

1//! Calendar types for the Nylas API v3.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{CalendarId, GrantId, ResourceId};
6
7/// A calendar object from the Nylas API.
8///
9/// Calendars represent a user's calendars from various providers (Google, Microsoft, etc.).
10///
11/// # Example
12///
13/// ```
14/// # use nylas_types::{Calendar, CalendarId, GrantId};
15/// let calendar = Calendar {
16///     id: CalendarId::new("cal_123"),
17///     grant_id: GrantId::new("grant_123"),
18///     name: "Work Calendar".to_string(),
19///     description: Some("My work calendar".to_string()),
20///     location: None,
21///     timezone: "America/New_York".to_string(),
22///     is_primary: Some(true),
23///     read_only: false,
24///     is_owned_by_user: Some(true),
25///     hex_color: Some("#039be5".to_string()),
26///     hex_foreground_color: Some("#ffffff".to_string()),
27///     object: "calendar".to_string(),
28///     metadata: None,
29/// };
30/// ```
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32pub struct Calendar {
33    /// Unique identifier for the calendar.
34    pub id: CalendarId,
35
36    /// Grant ID associated with this calendar.
37    pub grant_id: GrantId,
38
39    /// The calendar's name.
40    pub name: String,
41
42    /// Description of the calendar.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub description: Option<String>,
45
46    /// Location associated with the calendar.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub location: Option<String>,
49
50    /// Timezone in IANA format (e.g., "America/New_York", "UTC").
51    pub timezone: String,
52
53    /// Whether this is the primary calendar (Google and EWS only).
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub is_primary: Option<bool>,
56
57    /// Whether the calendar is read-only.
58    #[serde(default)]
59    pub read_only: bool,
60
61    /// Whether the calendar is owned by the user.
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub is_owned_by_user: Option<bool>,
64
65    /// Hexadecimal color code for the calendar (Gmail and Microsoft Graph).
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub hex_color: Option<String>,
68
69    /// Hexadecimal foreground color code (Gmail and Microsoft Graph).
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub hex_foreground_color: Option<String>,
72
73    /// Object type identifier (always "calendar").
74    #[serde(default = "default_object_type")]
75    pub object: String,
76
77    /// Optional metadata for the calendar.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub metadata: Option<serde_json::Value>,
80}
81
82fn default_object_type() -> String {
83    "calendar".to_string()
84}
85
86/// Request body for creating a calendar.
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct CreateCalendarRequest {
89    /// The calendar's name.
90    pub name: String,
91
92    /// Description of the calendar.
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub description: Option<String>,
95
96    /// Location associated with the calendar.
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub location: Option<String>,
99
100    /// Timezone in IANA format.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub timezone: Option<String>,
103
104    /// Optional metadata for the calendar.
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub metadata: Option<serde_json::Value>,
107}
108
109impl CreateCalendarRequest {
110    /// Create a new calendar request with a name.
111    pub fn new(name: impl Into<String>) -> Self {
112        Self {
113            name: name.into(),
114            description: None,
115            location: None,
116            timezone: None,
117            metadata: None,
118        }
119    }
120
121    /// Set the description.
122    pub fn description(mut self, description: impl Into<String>) -> Self {
123        self.description = Some(description.into());
124        self
125    }
126
127    /// Set the location.
128    pub fn location(mut self, location: impl Into<String>) -> Self {
129        self.location = Some(location.into());
130        self
131    }
132
133    /// Set the timezone.
134    pub fn timezone(mut self, timezone: impl Into<String>) -> Self {
135        self.timezone = Some(timezone.into());
136        self
137    }
138
139    /// Set metadata.
140    pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
141        self.metadata = Some(metadata);
142        self
143    }
144}
145
146/// Request body for updating a calendar.
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
148pub struct UpdateCalendarRequest {
149    /// The calendar's name.
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub name: Option<String>,
152
153    /// Description of the calendar.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub description: Option<String>,
156
157    /// Location associated with the calendar.
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub location: Option<String>,
160
161    /// Timezone in IANA format.
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub timezone: Option<String>,
164
165    /// Optional metadata for the calendar.
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub metadata: Option<serde_json::Value>,
168}
169
170impl UpdateCalendarRequest {
171    /// Create a new empty update request.
172    pub fn new() -> Self {
173        Self::default()
174    }
175
176    /// Set the name.
177    pub fn name(mut self, name: impl Into<String>) -> Self {
178        self.name = Some(name.into());
179        self
180    }
181
182    /// Set the description.
183    pub fn description(mut self, description: impl Into<String>) -> Self {
184        self.description = Some(description.into());
185        self
186    }
187
188    /// Set the location.
189    pub fn location(mut self, location: impl Into<String>) -> Self {
190        self.location = Some(location.into());
191        self
192    }
193
194    /// Set the timezone.
195    pub fn timezone(mut self, timezone: impl Into<String>) -> Self {
196        self.timezone = Some(timezone.into());
197        self
198    }
199
200    /// Set metadata.
201    pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
202        self.metadata = Some(metadata);
203        self
204    }
205}
206
207/// Organizational resource (conference room, equipment, etc.)
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
209pub struct CalendarResource {
210    /// Unique identifier for the resource
211    pub id: ResourceId,
212
213    /// Resource email address
214    pub email: String,
215
216    /// Resource name
217    pub name: String,
218
219    /// Resource type
220    #[serde(rename = "type")]
221    pub resource_type: ResourceType,
222
223    /// Building name
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub building: Option<String>,
226
227    /// Floor number
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub floor: Option<String>,
230
231    /// Capacity (number of people)
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub capacity: Option<i32>,
234
235    /// Features available (projector, whiteboard, etc.)
236    #[serde(default)]
237    pub features: Vec<String>,
238
239    /// Resource description
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub description: Option<String>,
242}
243
244/// Type of organizational resource.
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
246#[serde(rename_all = "lowercase")]
247pub enum ResourceType {
248    /// Conference room
249    Room,
250    /// Equipment (projector, etc.)
251    Equipment,
252    /// Shared workspace
253    Workspace,
254    /// Other resource type
255    Other,
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn test_calendar_creation() {
264        let calendar = Calendar {
265            id: CalendarId::new("cal_123"),
266            grant_id: GrantId::new("grant_123"),
267            name: "My Calendar".to_string(),
268            description: Some("Test calendar".to_string()),
269            location: None,
270            timezone: "UTC".to_string(),
271            is_primary: Some(true),
272            read_only: false,
273            is_owned_by_user: Some(true),
274            hex_color: Some("#039be5".to_string()),
275            hex_foreground_color: Some("#ffffff".to_string()),
276            object: "calendar".to_string(),
277            metadata: None,
278        };
279
280        assert_eq!(calendar.name, "My Calendar");
281        assert_eq!(calendar.timezone, "UTC");
282        assert_eq!(calendar.is_primary, Some(true));
283    }
284
285    #[test]
286    fn test_calendar_serialization() {
287        let calendar = Calendar {
288            id: CalendarId::new("cal_123"),
289            grant_id: GrantId::new("grant_123"),
290            name: "Test".to_string(),
291            description: None,
292            location: None,
293            timezone: "America/New_York".to_string(),
294            is_primary: Some(false),
295            read_only: false,
296            is_owned_by_user: Some(true),
297            hex_color: None,
298            hex_foreground_color: None,
299            object: "calendar".to_string(),
300            metadata: None,
301        };
302
303        let json = serde_json::to_string(&calendar).unwrap();
304        assert!(json.contains("cal_123"));
305        assert!(json.contains("Test"));
306        assert!(json.contains("America/New_York"));
307
308        let deserialized: Calendar = serde_json::from_str(&json).unwrap();
309        assert_eq!(deserialized, calendar);
310    }
311
312    #[test]
313    fn test_create_calendar_request_builder() {
314        let request = CreateCalendarRequest::new("Work Calendar")
315            .description("My work events")
316            .timezone("America/Los_Angeles")
317            .location("Office");
318
319        assert_eq!(request.name, "Work Calendar");
320        assert_eq!(request.description, Some("My work events".to_string()));
321        assert_eq!(request.timezone, Some("America/Los_Angeles".to_string()));
322        assert_eq!(request.location, Some("Office".to_string()));
323    }
324
325    #[test]
326    fn test_update_calendar_request_builder() {
327        let request = UpdateCalendarRequest::new()
328            .name("Updated Calendar")
329            .timezone("UTC");
330
331        assert_eq!(request.name, Some("Updated Calendar".to_string()));
332        assert_eq!(request.timezone, Some("UTC".to_string()));
333        assert_eq!(request.description, None);
334    }
335
336    #[test]
337    fn test_create_calendar_request_serialization() {
338        let request = CreateCalendarRequest::new("Test Calendar").description("Test description");
339
340        let json = serde_json::to_string(&request).unwrap();
341        assert!(json.contains("Test Calendar"));
342        assert!(json.contains("Test description"));
343
344        let deserialized: CreateCalendarRequest = serde_json::from_str(&json).unwrap();
345        assert_eq!(deserialized, request);
346    }
347
348    #[test]
349    fn test_resource_id_creation() {
350        let id = ResourceId::new("resource_123");
351        assert_eq!(id.as_str(), "resource_123");
352    }
353
354    #[test]
355    fn test_resource_type_serialization() {
356        let room = ResourceType::Room;
357        let json = serde_json::to_string(&room).unwrap();
358        assert_eq!(json, "\"room\"");
359    }
360
361    #[test]
362    fn test_resource_deserialization() {
363        let json = r#"{
364            "id": "res_123",
365            "email": "room1@company.com",
366            "name": "Conference Room 1",
367            "type": "room",
368            "capacity": 10,
369            "features": ["projector", "whiteboard"]
370        }"#;
371
372        let resource: CalendarResource = serde_json::from_str(json).unwrap();
373        assert_eq!(resource.name, "Conference Room 1");
374        assert_eq!(resource.capacity, Some(10));
375    }
376}