Add the RFC 5545 domain model

VEvent and friends, transcribed from the previous calendar-models crate
and tightened so that the states which caused its timezone and recurrence
bugs cannot be represented.

The substantive changes from v1:

CalendarDateTime is an enum over the four forms RFC 5545 actually admits
(date, floating, UTC, zoned) instead of a NaiveDateTime plus a loose
Option<String> zone plus an all_day flag that could all disagree. A zoned
value carries an IANA identifier, never a UTC offset -- an offset cannot
tell standard time from daylight time, which is why recurring events
drifted an hour across DST.

EventEnd is an enum, because DTEND and DURATION are mutually exclusive.
Priority validates 0-9 on construction and on deserialisation. EditScope
replaces dispatch on strings like "this_and_future", which appeared 53
times and turned typos into silent fallthrough.

CalendarObject models a CalDAV resource as it really is: one UID, one
master, N RECURRENCE-ID overrides. v1 flattened this to a bare event list,
which made overrides look like duplicates and motivated ~500 lines of
title-matching heuristics that silently discarded events.

Dropped VJournal, VFreeBusy, VTimeZone, TodoStatus, FreeBusyType and
Period: defined but never used.

28 tests cover serde round-trips, the exact JSON shape (the model is the
wire format, so changing it should be deliberate), duration fallbacks,
validation boundaries and master/override separation.

uuid needs an explicit entropy source on wasm; without it the frontend
cannot compile the shared model. Verified that the default feature set
pulls in neither icalendar, rrule, chrono-tz, quick-xml nor reqwest.
This commit is contained in:
2026-08-26 12:29:59 -04:00
parent bf63024711
commit 52cd5a961d
10 changed files with 1344 additions and 4 deletions
+6
View File
@@ -23,6 +23,12 @@ chrono-tz = { workspace = true, optional = true }
icalendar = { workspace = true, optional = true }
rrule = { workspace = true, optional = true }
# On wasm there is no OS entropy source, so uuid needs to be pointed at the
# browser's crypto API explicitly. Without this the frontend cannot compile the
# shared model at all -- worth catching in CI, not at deploy time.
[target.'cfg(target_arch = "wasm32")'.dependencies]
uuid = { workspace = true, features = ["js"] }
[dev-dependencies]
serde_json = { workspace = true }
pretty_assertions = { workspace = true }