//! A CalDAV calendar object resource — one `.ics` file on the server. //! //! This is the level at which CalDAV actually operates, and modelling it //! explicitly is what makes correct recurrence handling possible. //! //! A single resource holds a `VCALENDAR` containing one or more `VEVENT`s that //! all share a `UID`: the series master, plus one override per modified //! occurrence, each distinguished by its `RECURRENCE-ID`. The previous //! iteration flattened that structure into a bare list of events, which made //! masters and overrides indistinguishable — so a correctly-formed feed looked //! like it contained duplicates, and ~500 lines of title-matching heuristics //! were added to "clean up" the result by silently discarding events. Keeping //! the resource intact removes the need for any of that. use super::calendar::VCalendar; use super::event::VEvent; use serde::{Deserialize, Serialize}; /// One addressable resource on a CalDAV server, with its concurrency metadata. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct CalendarObject { /// Path of this resource on the server, relative to its host. pub href: String, /// Server-assigned `ETag`. Absent for an object not yet written. #[serde(default, skip_serializing_if = "Option::is_none")] pub etag: Option, /// Path of the collection holding this resource. pub calendar_path: String, /// The `VCALENDAR` this resource contains — events, and the zone /// definitions they reference. Both halves have to travel together: a `PUT` /// that dropped the `VTIMEZONE` would leave the resource unreadable to /// clients that cannot resolve the identifier on their own. pub calendar: VCalendar, } impl CalendarObject { /// The `VEVENT`s inside, sharing one `UID`: at most one master plus any /// number of overrides. pub fn events(&self) -> &[VEvent] { &self.calendar.events } /// The series master — the event without a `RECURRENCE-ID`. /// /// Absent when a resource contains only overrides, which is unusual but /// legal and which servers do emit. pub fn master(&self) -> Option<&VEvent> { self.calendar.master() } /// The overrides, each replacing one occurrence of the master. pub fn overrides(&self) -> impl Iterator { self.calendar.overrides() } /// The `UID` shared by every event in this resource. pub fn uid(&self) -> Option<&str> { self.calendar.events.first().map(|e| e.uid.as_str()) } /// Whether every contained event agrees on the `UID`, as RFC 4791 requires. /// /// Worth asserting in tests against real servers rather than assuming. pub fn has_consistent_uid(&self) -> bool { let mut uids = self.calendar.events.iter().map(|e| e.uid.as_str()); match uids.next() { None => true, Some(first) => uids.all(|u| u == first), } } } /// Which occurrences of a series an edit or deletion applies to. /// /// Replaces the previous iteration's stringly-typed dispatch on values like /// `"this_only"`, `"this_and_future"` and `"delete_series"`, which appeared 53 /// times across the codebase and turned a typo into a silent runtime /// fallthrough rather than a compile error. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum EditScope { /// Just the occurrence named by the request. Realised as an `EXDATE` on the /// master plus a new override carrying a `RECURRENCE-ID`. #[default] ThisOnly, /// This occurrence and everything after it. Realised by truncating the /// master with `UNTIL` and creating a fresh series. ThisAndFuture, /// Every occurrence. Realised by editing the master in place. EntireSeries, }