nylas_types/
event.rs

1//! Event types for the Nylas API v3.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{CalendarId, EmailAddress, EventId, GrantId};
6
7/// An event object from the Nylas API.
8///
9/// Events represent calendar events from various providers (Google, Microsoft, etc.).
10///
11/// # Example
12///
13/// ```
14/// # use nylas_types::{Event, EventId, GrantId, CalendarId, When, EventStatus};
15/// let event = Event {
16///     id: EventId::new("event_123"),
17///     grant_id: GrantId::new("grant_123"),
18///     calendar_id: CalendarId::new("cal_123"),
19///     title: Some("Team Meeting".to_string()),
20///     description: Some("Weekly team sync".to_string()),
21///     when: When::Timespan {
22///         start_time: 1234567890,
23///         end_time: 1234571490,
24///         start_timezone: Some("America/New_York".to_string()),
25///         end_timezone: Some("America/New_York".to_string()),
26///     },
27///     location: Some("Conference Room A".to_string()),
28///     busy: true,
29///     status: EventStatus::Confirmed,
30///     participants: vec![],
31///     organizer: None,
32///     creator: None,
33///     conferencing: None,
34///     recurrence: None,
35///     reminders: None,
36///     capacity: None,
37///     hide_participants: false,
38///     read_only: false,
39///     html_link: None,
40///     ical_uid: None,
41///     resources: vec![],
42///     visibility: None,
43///     object: "event".to_string(),
44///     metadata: None,
45///     created_at: Some(1234567890),
46///     updated_at: Some(1234567890),
47/// };
48/// ```
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub struct Event {
51    /// Unique identifier for the event.
52    pub id: EventId,
53
54    /// Grant ID associated with this event.
55    pub grant_id: GrantId,
56
57    /// Calendar ID this event belongs to.
58    pub calendar_id: CalendarId,
59
60    /// Title of the event.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub title: Option<String>,
63
64    /// Description of the event.
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub description: Option<String>,
67
68    /// When the event occurs.
69    pub when: When,
70
71    /// Location of the event.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub location: Option<String>,
74
75    /// Whether the event shows as busy.
76    #[serde(default = "default_busy")]
77    pub busy: bool,
78
79    /// Status of the event.
80    #[serde(default)]
81    pub status: EventStatus,
82
83    /// List of participants.
84    #[serde(default)]
85    pub participants: Vec<Participant>,
86
87    /// Organizer of the event.
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub organizer: Option<EmailAddress>,
90
91    /// Creator of the event.
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub creator: Option<EmailAddress>,
94
95    /// Conference details.
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub conferencing: Option<Conferencing>,
98
99    /// Recurrence rules for repeating events.
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub recurrence: Option<Recurrence>,
102
103    /// Reminder settings.
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub reminders: Option<Reminders>,
106
107    /// Maximum capacity for the event.
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub capacity: Option<i32>,
110
111    /// Whether to hide participants from each other.
112    #[serde(default)]
113    pub hide_participants: bool,
114
115    /// Whether the event is read-only.
116    #[serde(default)]
117    pub read_only: bool,
118
119    /// Link to view the event in the provider's interface.
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub html_link: Option<String>,
122
123    /// iCalendar UID for the event.
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub ical_uid: Option<String>,
126
127    /// Resources (rooms, equipment) booked for the event.
128    #[serde(default)]
129    pub resources: Vec<Resource>,
130
131    /// Visibility of the event (default, public, private).
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub visibility: Option<EventVisibility>,
134
135    /// Object type identifier (always "event").
136    #[serde(default = "default_event_object_type")]
137    pub object: String,
138
139    /// Optional metadata for the event.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub metadata: Option<serde_json::Value>,
142
143    /// Unix timestamp when the event was created.
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub created_at: Option<i64>,
146
147    /// Unix timestamp when the event was last updated.
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub updated_at: Option<i64>,
150}
151
152fn default_busy() -> bool {
153    true
154}
155
156fn default_event_object_type() -> String {
157    "event".to_string()
158}
159
160/// When the event occurs.
161///
162/// This can be a specific timespan, an all-day event, or a datespan.
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164#[serde(tag = "object", rename_all = "lowercase")]
165pub enum When {
166    /// A specific time period with start and end times.
167    Timespan {
168        /// Unix timestamp for the start time.
169        start_time: i64,
170
171        /// Unix timestamp for the end time.
172        end_time: i64,
173
174        /// Timezone for the start time (IANA format).
175        #[serde(skip_serializing_if = "Option::is_none")]
176        start_timezone: Option<String>,
177
178        /// Timezone for the end time (IANA format).
179        #[serde(skip_serializing_if = "Option::is_none")]
180        end_timezone: Option<String>,
181    },
182
183    /// An all-day event spanning multiple days.
184    Datespan {
185        /// Start date in YYYY-MM-DD format.
186        start_date: String,
187
188        /// End date in YYYY-MM-DD format.
189        end_date: String,
190    },
191
192    /// A single all-day event.
193    Date {
194        /// Date in YYYY-MM-DD format.
195        date: String,
196    },
197}
198
199/// Event status.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
201#[serde(rename_all = "lowercase")]
202pub enum EventStatus {
203    /// Event is confirmed.
204    #[default]
205    Confirmed,
206
207    /// Event is tentative.
208    Tentative,
209
210    /// Event is cancelled.
211    Cancelled,
212}
213
214/// Event visibility.
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
216#[serde(rename_all = "lowercase")]
217pub enum EventVisibility {
218    /// Default visibility.
219    Default,
220
221    /// Public event.
222    Public,
223
224    /// Private event.
225    Private,
226}
227
228/// A participant in an event.
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
230pub struct Participant {
231    /// Email address of the participant.
232    pub email: String,
233
234    /// Name of the participant.
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub name: Option<String>,
237
238    /// RSVP status of the participant.
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub status: Option<ParticipantStatus>,
241
242    /// Comment from the participant.
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub comment: Option<String>,
245
246    /// Phone number (Microsoft Graph only).
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub phone_number: Option<String>,
249}
250
251impl Participant {
252    /// Create a new participant with an email address.
253    pub fn new(email: impl Into<String>) -> Self {
254        Self {
255            email: email.into(),
256            name: None,
257            status: None,
258            comment: None,
259            phone_number: None,
260        }
261    }
262
263    /// Set the participant's name.
264    pub fn name(mut self, name: impl Into<String>) -> Self {
265        self.name = Some(name.into());
266        self
267    }
268
269    /// Set the participant's RSVP status.
270    pub fn status(mut self, status: ParticipantStatus) -> Self {
271        self.status = Some(status);
272        self
273    }
274}
275
276/// Participant RSVP status.
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
278#[serde(rename_all = "lowercase")]
279pub enum ParticipantStatus {
280    /// Accepted.
281    Yes,
282
283    /// Declined.
284    No,
285
286    /// Maybe attending.
287    Maybe,
288
289    /// No reply yet.
290    Noreply,
291}
292
293/// Conference details for an event.
294#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
295pub struct Conferencing {
296    /// Conferencing provider (e.g., "Google Meet", "Microsoft Teams", "Zoom Meeting").
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub provider: Option<String>,
299
300    /// Conference details.
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub details: Option<ConferencingDetails>,
303}
304
305/// Details for a conference.
306#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
307pub struct ConferencingDetails {
308    /// URL for the virtual meeting.
309    pub url: String,
310
311    /// Meeting code.
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub meeting_code: Option<String>,
314
315    /// Password for the meeting.
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub password: Option<String>,
318
319    /// PIN for the meeting.
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub pin: Option<String>,
322
323    /// Dial-in phone numbers.
324    #[serde(default)]
325    pub phone: Vec<String>,
326}
327
328/// Recurrence rules for repeating events.
329#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
330pub struct Recurrence {
331    /// Recurrence rule in RRULE format (e.g., "FREQ=WEEKLY;BYDAY=MO,WE,FR").
332    #[serde(rename = "rrule")]
333    pub rrule: Vec<String>,
334
335    /// Exception dates in EXDATE format (e.g., "20240101T120000Z").
336    #[serde(default)]
337    pub exdate: Vec<String>,
338}
339
340/// Reminder settings for an event.
341#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
342pub struct Reminders {
343    /// Whether to use default calendar reminders.
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub use_default: Option<bool>,
346
347    /// Custom reminder overrides.
348    #[serde(default)]
349    pub overrides: Vec<ReminderOverride>,
350}
351
352/// A custom reminder override.
353#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
354pub struct ReminderOverride {
355    /// Number of minutes before the event to trigger the reminder.
356    pub reminder_minutes: i32,
357
358    /// Reminder method (email, popup, display, sound).
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub reminder_method: Option<String>,
361}
362
363/// A resource (room, equipment) for an event.
364#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
365pub struct Resource {
366    /// Email address of the resource.
367    pub email: String,
368
369    /// Name of the resource.
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub name: Option<String>,
372
373    /// Resource type.
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub object: Option<String>,
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[test]
383    fn test_event_creation() {
384        let event = Event {
385            id: EventId::new("event_123"),
386            grant_id: GrantId::new("grant_123"),
387            calendar_id: CalendarId::new("cal_123"),
388            title: Some("Test Event".to_string()),
389            description: None,
390            when: When::Timespan {
391                start_time: 1234567890,
392                end_time: 1234571490,
393                start_timezone: Some("UTC".to_string()),
394                end_timezone: Some("UTC".to_string()),
395            },
396            location: None,
397            busy: true,
398            status: EventStatus::Confirmed,
399            participants: vec![],
400            organizer: None,
401            creator: None,
402            conferencing: None,
403            recurrence: None,
404            reminders: None,
405            capacity: None,
406            hide_participants: false,
407            read_only: false,
408            html_link: None,
409            ical_uid: None,
410            resources: vec![],
411            visibility: None,
412            object: "event".to_string(),
413            metadata: None,
414            created_at: None,
415            updated_at: None,
416        };
417
418        assert_eq!(event.title, Some("Test Event".to_string()));
419        assert!(event.busy);
420        assert_eq!(event.status, EventStatus::Confirmed);
421    }
422
423    #[test]
424    fn test_when_timespan_serialization() {
425        let when = When::Timespan {
426            start_time: 1234567890,
427            end_time: 1234571490,
428            start_timezone: Some("America/New_York".to_string()),
429            end_timezone: Some("America/New_York".to_string()),
430        };
431
432        let json = serde_json::to_string(&when).unwrap();
433        assert!(json.contains("timespan"));
434        assert!(json.contains("1234567890"));
435        assert!(json.contains("America/New_York"));
436
437        let deserialized: When = serde_json::from_str(&json).unwrap();
438        assert_eq!(deserialized, when);
439    }
440
441    #[test]
442    fn test_when_datespan_serialization() {
443        let when = When::Datespan {
444            start_date: "2024-01-01".to_string(),
445            end_date: "2024-01-03".to_string(),
446        };
447
448        let json = serde_json::to_string(&when).unwrap();
449        assert!(json.contains("datespan"));
450        assert!(json.contains("2024-01-01"));
451        assert!(json.contains("2024-01-03"));
452
453        let deserialized: When = serde_json::from_str(&json).unwrap();
454        assert_eq!(deserialized, when);
455    }
456
457    #[test]
458    fn test_when_date_serialization() {
459        let when = When::Date {
460            date: "2024-01-15".to_string(),
461        };
462
463        let json = serde_json::to_string(&when).unwrap();
464        assert!(json.contains("date"));
465        assert!(json.contains("2024-01-15"));
466
467        let deserialized: When = serde_json::from_str(&json).unwrap();
468        assert_eq!(deserialized, when);
469    }
470
471    #[test]
472    fn test_participant_builder() {
473        let participant = Participant::new("user@example.com")
474            .name("John Doe")
475            .status(ParticipantStatus::Yes);
476
477        assert_eq!(participant.email, "user@example.com");
478        assert_eq!(participant.name, Some("John Doe".to_string()));
479        assert_eq!(participant.status, Some(ParticipantStatus::Yes));
480    }
481
482    #[test]
483    fn test_participant_status_serialization() {
484        let status = ParticipantStatus::Yes;
485        let json = serde_json::to_string(&status).unwrap();
486        assert_eq!(json, "\"yes\"");
487
488        let status = ParticipantStatus::Maybe;
489        let json = serde_json::to_string(&status).unwrap();
490        assert_eq!(json, "\"maybe\"");
491    }
492
493    #[test]
494    fn test_event_status_serialization() {
495        let status = EventStatus::Confirmed;
496        let json = serde_json::to_string(&status).unwrap();
497        assert_eq!(json, "\"confirmed\"");
498
499        let status = EventStatus::Cancelled;
500        let json = serde_json::to_string(&status).unwrap();
501        assert_eq!(json, "\"cancelled\"");
502    }
503
504    #[test]
505    fn test_conferencing_serialization() {
506        let conferencing = Conferencing {
507            provider: Some("Google Meet".to_string()),
508            details: Some(ConferencingDetails {
509                url: "https://meet.google.com/abc-def-ghi".to_string(),
510                meeting_code: Some("abc-def-ghi".to_string()),
511                password: None,
512                pin: None,
513                phone: vec![],
514            }),
515        };
516
517        let json = serde_json::to_string(&conferencing).unwrap();
518        assert!(json.contains("Google Meet"));
519        assert!(json.contains("meet.google.com"));
520
521        let deserialized: Conferencing = serde_json::from_str(&json).unwrap();
522        assert_eq!(deserialized, conferencing);
523    }
524}