Skip to main content

agentmail/types/
calendar.rs

1use serde::{Deserialize, Serialize};
2
3use crate::util::QueryBuilder;
4
5/// An inbox's calendar, from `get_calendar` / `update_calendar`.
6#[derive(Clone, Debug, Deserialize)]
7pub struct Calendar {
8    /// The inbox the calendar belongs to.
9    pub inbox_id: String,
10    /// IANA timezone the calendar renders in, e.g. `America/New_York`.
11    pub timezone: String,
12    /// Number of events on the calendar.
13    pub event_count: u64,
14    /// Creation timestamp (RFC 3339).
15    pub created_at: String,
16    /// Last-update timestamp (RFC 3339).
17    pub updated_at: String,
18    /// Opaque revision token; pass it as `etag` on writes to reject
19    /// concurrent modifications (428 otherwise).
20    pub etag: String,
21}
22
23/// Whether an event occurs once or as part of a series.
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum EventKind {
27    /// A one-off event.
28    Single,
29    /// The defining event of a recurring series.
30    Series,
31    /// One materialized occurrence of a series.
32    Instance,
33    /// A kind this client version does not recognize.
34    #[serde(other)]
35    Unknown,
36}
37
38/// Confirmation state of an event.
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "lowercase")]
41pub enum EventStatus {
42    /// The event is on as scheduled.
43    Confirmed,
44    /// The event is provisional.
45    Tentative,
46    /// The event was cancelled.
47    Cancelled,
48    /// A status this client version does not recognize.
49    #[serde(other)]
50    Unknown,
51}
52
53/// How an event's `duration_value` is interpreted.
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "lowercase")]
56pub enum DurationMode {
57    /// `duration_value` counts nominal calendar units (a `P1D` day stays a day
58    /// across DST shifts).
59    Nominal,
60    /// `duration_value` counts exact seconds.
61    Exact,
62    /// A mode this client version does not recognize.
63    #[serde(other)]
64    Unknown,
65}
66
67/// Where an event came from.
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
69#[serde(rename_all = "lowercase")]
70pub enum EventSource {
71    /// Created through the API.
72    Api,
73    /// Parsed out of an emailed invite.
74    Email,
75    /// A source this client version does not recognize.
76    #[serde(other)]
77    Unknown,
78}
79
80/// The organizer's response state on an event the inbox was invited to.
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
82#[serde(rename_all = "snake_case")]
83pub enum ResponseStatus {
84    /// No reply yet.
85    NeedsAction,
86    /// Going.
87    Accepted,
88    /// Not going.
89    Declined,
90    /// Maybe.
91    Tentative,
92    /// A status this client version does not recognize.
93    #[serde(other)]
94    Unknown,
95}
96
97/// An attendee on an event.
98#[derive(Clone, Debug, Default, Serialize, Deserialize)]
99pub struct CalendarAttendee {
100    /// The attendee's email.
101    pub email: String,
102    /// Display name, when known.
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub name: Option<String>,
105    /// The attendee's reply state.
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub status: Option<String>,
108    /// `required` or `optional`.
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub role: Option<String>,
111    /// The attendee's reply comment, when they left one.
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub comment: Option<String>,
114    /// When the attendee last replied (RFC 3339).
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub responded_at: Option<String>,
117}
118
119/// Recurrence rule and exceptions on an event. `rdates` entries are either an
120/// RFC 3339 timestamp or a `{start, end?, duration?}` object, so they are kept
121/// as raw JSON.
122#[derive(Clone, Debug, Default, Serialize, Deserialize)]
123pub struct Recurrence {
124    /// RFC 5545 RRULE, e.g. `FREQ=WEEKLY;BYDAY=MO,WE`.
125    pub rule: String,
126    /// Occurrence start timestamps to exclude (RFC 3339).
127    #[serde(default, skip_serializing_if = "Vec::is_empty")]
128    pub exdates: Vec<String>,
129    /// Extra occurrences to add: RFC 3339 strings or `{start, end?, duration?}`.
130    #[serde(default, skip_serializing_if = "Vec::is_empty")]
131    pub rdates: Vec<serde_json::Value>,
132    /// Cutoff after which the series stops recurring (RFC 3339).
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub truncate_before: Option<String>,
135}
136
137/// A calendar event, as the API returns it.
138#[derive(Clone, Debug, Deserialize)]
139pub struct CalendarEvent {
140    /// Unique event id.
141    pub event_id: String,
142    /// Whether this is a single event, a series definition, or an instance.
143    #[serde(default)]
144    pub kind: Option<EventKind>,
145    /// The series this instance belongs to, when applicable.
146    #[serde(default)]
147    pub series_id: Option<String>,
148    /// The series' start this instance shifted from, when moved (RFC 3339).
149    #[serde(default)]
150    pub original_start: Option<String>,
151    /// Same as [`CalendarEvent::original_start`], on `instance` events.
152    #[serde(default)]
153    pub original_start_at: Option<String>,
154    /// Whether this instance deviates from the series.
155    #[serde(default)]
156    pub is_exception: Option<bool>,
157    /// Event title.
158    pub title: String,
159    /// Long description.
160    #[serde(default)]
161    pub description: Option<String>,
162    /// Location, when set.
163    #[serde(default)]
164    pub location: Option<String>,
165    /// Your own JSON stored alongside the event.
166    #[serde(default)]
167    pub metadata: Option<serde_json::Value>,
168    /// Confirmation state.
169    #[serde(default)]
170    pub status: Option<EventStatus>,
171    /// Whether the event lasts all day (then `start`/`end` are dates).
172    pub all_day: bool,
173    /// Display start (RFC 3339, or a date for all-day events).
174    pub start: String,
175    /// Display end.
176    pub end: String,
177    /// IANA timezone the event renders in.
178    pub timezone: String,
179    /// How `duration_value` is interpreted.
180    #[serde(default)]
181    pub duration_mode: Option<DurationMode>,
182    /// Length in units picked by `duration_mode`.
183    pub duration_value: i64,
184    /// Absolute start (RFC 3339).
185    pub start_at: String,
186    /// Absolute end (RFC 3339).
187    pub end_at: String,
188    /// Recurrence rule, when the event repeats.
189    #[serde(default)]
190    pub recurrence: Option<Recurrence>,
191    /// Invitees and their replies.
192    #[serde(default)]
193    pub attendees: Vec<CalendarAttendee>,
194    /// Number of attendees.
195    pub attendee_count: u32,
196    /// The inbox's own reply state, when it was invited.
197    #[serde(default)]
198    pub response_status: Option<ResponseStatus>,
199    /// iCalendar UID, stable across the series.
200    pub uid: String,
201    /// iCalendar sequence number, bumped on material changes.
202    pub sequence: u32,
203    /// Whether the event came from the API or an email.
204    #[serde(default)]
205    pub source: Option<EventSource>,
206    /// Organizer address, when known.
207    #[serde(default)]
208    pub organizer_email: Option<String>,
209    /// The email message the invite arrived in, when from email.
210    #[serde(default)]
211    pub origin_message_id: Option<String>,
212    /// Monotonic revision, for optimistic concurrency.
213    #[serde(default)]
214    pub resource_revision: Option<u64>,
215    /// Opaque revision token for `If-Match` writes, when returned.
216    #[serde(default)]
217    pub etag: Option<String>,
218    /// Creation timestamp (RFC 3339).
219    pub created_at: String,
220    /// Last-update timestamp (RFC 3339).
221    pub updated_at: String,
222}
223
224/// Read-staleness control for calendar reads. `primary` (the API default)
225/// reads the authoritative store; `eventual` may serve a replica.
226#[derive(Clone, Copy, Debug, PartialEq, Eq)]
227pub enum Consistency {
228    /// May read a replica; cheaper and usually fine.
229    Eventual,
230    /// Always read the primary store.
231    Primary,
232}
233
234impl Consistency {
235    pub(crate) fn as_str(self) -> &'static str {
236        match self {
237            Consistency::Eventual => "eventual",
238            Consistency::Primary => "primary",
239        }
240    }
241}
242
243/// Which occurrences a series mutation applies to.
244#[derive(Clone, Copy, Debug, PartialEq, Eq)]
245pub enum MutationMode {
246    /// Only the addressed instance.
247    Single,
248    /// The addressed instance and every later one.
249    Future,
250}
251
252impl MutationMode {
253    pub(crate) fn as_str(self) -> &'static str {
254        match self {
255            MutationMode::Single => "single",
256            MutationMode::Future => "future",
257        }
258    }
259}
260
261/// Windowing and pagination for `get_agenda` and `list_event_instances`.
262#[derive(Clone, Debug, Default)]
263pub struct CalendarEventsQuery {
264    /// Only occurrences starting at or after this instant (RFC 3339).
265    pub after: Option<String>,
266    /// Only occurrences starting before this instant (RFC 3339).
267    pub before: Option<String>,
268    /// Also include occurrences overlapping the window.
269    pub include_overlapping: Option<bool>,
270    /// Maximum occurrences per page.
271    pub limit: Option<u32>,
272    /// Cursor from a previous response's `next_page_token`.
273    pub page_token: Option<String>,
274    /// Read-staleness control.
275    pub consistency: Option<Consistency>,
276}
277
278impl CalendarEventsQuery {
279    pub(crate) fn query(&self) -> Vec<(&'static str, String)> {
280        QueryBuilder::new()
281            .opt("after", self.after.as_ref())
282            .opt("before", self.before.as_ref())
283            .opt("include_overlapping", self.include_overlapping.as_ref())
284            .opt("limit", self.limit.as_ref())
285            .opt("page_token", self.page_token.as_ref())
286            .opt(
287                "consistency",
288                self.consistency.map(Consistency::as_str).as_ref(),
289            )
290            .build()
291    }
292}
293
294/// Body for `create_calendar_event`. `title`, `start`, and `end` are required
295/// by the API.
296#[derive(Clone, Debug, Default, Serialize)]
297pub struct CreateCalendarEvent {
298    /// Your own idempotency/reference id for the event.
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub client_id: Option<String>,
301    /// Event title.
302    pub title: String,
303    /// Long description.
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub description: Option<String>,
306    /// Location.
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub location: Option<String>,
309    /// Your own JSON stored alongside the event.
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub metadata: Option<serde_json::Value>,
312    /// Confirmation state.
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub status: Option<EventStatus>,
315    /// All-day event (`start`/`end` become dates).
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub all_day: Option<bool>,
318    /// Display start (RFC 3339, or a date for all-day events).
319    pub start: String,
320    /// Display end.
321    pub end: String,
322    /// IANA timezone; defaults to the calendar's.
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub timezone: Option<String>,
325    /// How to interpret `duration_value` (defaults to the API's).
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub duration_mode: Option<DurationMode>,
328    /// Recurrence rule, to make the event a series.
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub recurrence: Option<Recurrence>,
331    /// Invitees.
332    #[serde(skip_serializing_if = "Vec::is_empty")]
333    pub attendees: Vec<CalendarAttendee>,
334    /// Email invites to the attendees.
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub send_invites: Option<bool>,
337}
338
339/// Body for `update_calendar_event`. Fields left `None` stay unchanged; there
340/// is no way to set a field back to null from this client (the API accepts
341/// nulls for `description`/`location`/`metadata`/`recurrence`, but `None`
342/// here means "leave alone", matching [`crate::UpdateDraft`]).
343#[derive(Clone, Debug, Default, Serialize)]
344pub struct UpdateCalendarEvent {
345    /// Replace the title.
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub title: Option<String>,
348    /// Replace the description.
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub description: Option<String>,
351    /// Replace the location.
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub location: Option<String>,
354    /// Replace the stored metadata.
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub metadata: Option<serde_json::Value>,
357    /// Replace the confirmation state.
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub status: Option<EventStatus>,
360    /// Switch to or from an all-day event.
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub all_day: Option<bool>,
363    /// Replace the display start.
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub start: Option<String>,
366    /// Replace the display end.
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub end: Option<String>,
369    /// Replace the IANA timezone.
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub timezone: Option<String>,
372    /// Replace the duration interpretation.
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub duration_mode: Option<DurationMode>,
375    /// Replace the recurrence rule.
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub recurrence: Option<Recurrence>,
378    /// Replace the attendee list.
379    #[serde(skip_serializing_if = "Vec::is_empty")]
380    pub attendees: Vec<CalendarAttendee>,
381    /// Email invites to (new) attendees.
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub send_invites: Option<bool>,
384}
385
386/// Body for `respond_to_calendar_event`: the inbox's reply to an invite.
387#[derive(Clone, Debug, Default, Serialize)]
388pub struct RespondToCalendarEvent {
389    /// The reply: `accepted`, `declined`, or `tentative`.
390    pub status: String,
391    /// A note sent along with the reply.
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub comment: Option<String>,
394    /// Email the reply to the organizer.
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub send_reply: Option<bool>,
397}
398
399/// One page of occurrences from `get_agenda` or `list_event_instances`.
400#[derive(Clone, Debug, Deserialize)]
401pub struct CalendarEventList {
402    /// Total occurrences in the window (not just this page).
403    pub count: u64,
404    /// This page of occurrences.
405    #[serde(default)]
406    pub events: Vec<CalendarEvent>,
407    /// Cursor for the next page; `None` on the last page.
408    #[serde(default)]
409    pub next_page_token: Option<String>,
410}
411
412/// The response to an event create/update: the mutated event plus the
413/// operation id (used by the API's event stream to ack the change).
414#[derive(Clone, Debug, Deserialize)]
415pub struct CalendarEventMutation {
416    /// The event after the mutation.
417    pub event: CalendarEvent,
418    /// Id of the applied operation.
419    #[serde(default)]
420    pub operation_id: Option<String>,
421}
422
423/// The response to `delete_calendar_event`; `event` carries the deleted state
424/// when the API returns it.
425#[derive(Clone, Debug, Deserialize)]
426pub struct DeleteCalendarEventResult {
427    /// Id of the deletion operation, when the API issues one.
428    #[serde(default)]
429    pub deletion_id: Option<String>,
430    /// The event as it was, when the API returns it.
431    #[serde(default)]
432    pub event: Option<CalendarEvent>,
433}