jmap-calendars-types
Serde-annotated Rust types for JMAP Calendars (draft-ietf-jmap-calendars-26) and JSCalendar (RFC 8984).
Types only — no method handlers, no async, no network I/O. Sits between
jmap-types (RFC 8620 primitives) and jmap-calendars-server /
jmap-calendars-client.
What it is
| Type | Module | Spec |
|---|---|---|
Calendar |
calendar |
draft §4 |
CalendarRights |
calendar |
draft §4 |
IncludeInAvailability |
calendar |
draft §4 |
CalendarFilterCondition |
calendar |
draft §4.5 |
CalendarEvent |
event |
draft §5, RFC 8984 §2.1, §4, §5.1 |
CalendarEventFilterCondition |
event |
draft §5.11.1 |
CalendarEventComparator |
event |
draft §5.11.2 |
RecurrenceRule |
jscalendar |
RFC 8984 §4.3.3 |
NDay |
jscalendar |
RFC 8984 §4.3.3 |
Location |
jscalendar |
RFC 8984 §4.2.5 |
VirtualLocation |
jscalendar |
RFC 8984 §4.2.6 |
Link |
jscalendar |
RFC 8984 §1.4.11 |
Participant |
jscalendar |
RFC 8984 §4.4.6 |
Alert |
jscalendar |
RFC 8984 §4.5.2 |
AlertTrigger |
jscalendar |
RFC 8984 §4.5.2 |
OffsetTrigger |
jscalendar |
RFC 8984 §4.5.2 |
AbsoluteTrigger |
jscalendar |
RFC 8984 §4.5.2 |
Relation |
jscalendar |
RFC 8984 §1.4.10 |
LocalDateTime |
jscalendar |
RFC 8984 §1.4.5 |
Duration |
jscalendar |
RFC 8984 §1.4.6 |
SignedDuration |
jscalendar |
RFC 8984 §1.4.7 |
CalendarEventNotification |
notification |
draft §7 |
Person |
notification |
draft §7 |
NotificationType |
notification |
draft §7 |
NotificationFilterCondition |
notification |
draft §7.4.1 |
CalendarAlert |
notification |
draft §7 |
ParticipantIdentity |
participant_identity |
draft §3 |
BusyPeriod |
availability |
draft §2.2 |
CalendarsCapability |
capability |
draft §1.5 |
CalendarsAccountCapability |
capability |
draft §1.5 |
CalendarsParseCapability |
capability |
draft §5.13 |
PrincipalCalendarsCapability |
capability |
draft §1.5 |
PrincipalsAvailabilityCapability |
capability |
draft §2.2 |
PrincipalsAvailabilityAccountCapability |
capability |
draft §2.2 |
The types in the jscalendar module (rows above with module = jscalendar) live in
the jmap-jscalendar-types crate and are re-exported here for backward-compatible
access. The re-export is available both as flat re-exports (jmap_calendars_types::Location)
and as a module alias (jmap_calendars_types::jscalendar::Location). The same sub-types
are consumed by jmap-tasks-types (planned).
What it's for
draft-ietf-jmap-calendars-26 data types, consumed by
jmap-calendars-server (method handlers + the CalendarsBackend trait)
and jmap-calendars-client (typed method bindings). Re-exports
jmap-jscalendar-types as the jscalendar module so callers can opt
into typed RFC 8984 JSCalendar sub-objects without taking an extra
direct dependency. Sibling to jmap-mail-types, jmap-contacts-types,
and jmap-tasks-types in the workspace's extension-types family.
Property enum types re-exported from the backend sub-module:
CalendarProperty, CalendarEventProperty, CalendarEventNotificationProperty,
ParticipantIdentityProperty.
Capability URI string constants: JMAP_CALENDARS_URI,
JMAP_CALENDARS_PARSE_URI, JMAP_PRINCIPALS_AVAILABILITY_URI.
Filter extensibility
Filter and comparator types in this crate — CalendarFilterCondition,
CalendarEventFilterCondition, CalendarEventComparator,
NotificationFilterCondition, and the generic Filter<T> / Operator
re-exported from jmap-types — are intentionally not extensible via
vendor "extras" fields. A filter clause the server does not understand
silently breaks query correctness: the client gets the wrong set of records
back with no error signal. So these types deliberately have no extra
catch-all field.
Vendors who need to filter on custom fields have two options:
- IETF-track (recommended). Use the JMAP Object Metadata extension
(
draft-ietf-jmap-metadata, capability URIurn:ietf:params:jmap:metadata), (draft-ietf-jmap-metadata-02), which adds namespace-keyedmetadata/privateMetadataproperties to each opted-in data type, with capability-declared schema (dataTypes→namespaces/supportsVendorNamespaces/maxDepth, §1.2.1) andmetadataExists/metadataTextContains/metadataTextEqualsconditions on the type's own/queryfilter (§3.5). This is the workspace's recommended path for vendor data that needs to be queryable. Helpers live injmap-metadata-types,jmap-metadata-server, andjmap-metadata-client. - Pre-IETF escape. If you cannot wait for the metadata draft, escape the
filter tree to
serde_json::Valueor fork theFilterConditiontypes. SeePLAN.mdin this crate for the hybrid sloppy-value pattern this crate already uses for JSCalendar-shaped fields.
This policy is part of the workspace extras-preservation policy documented in
the workspace AGENTS.md; the filter-algebra exclusion
decision is bd JMAP-lbdy.
Spec coverage
draft-ietf-jmap-calendars-26
| Section | Topic |
|---|---|
| §1.5 | CalendarsCapability, CalendarsAccountCapability |
| §2.2 | Principal/getAvailability — BusyPeriod |
| §3 | ParticipantIdentity |
| §4 | Calendar, CalendarRights, IncludeInAvailability |
| §5 | CalendarEvent JMAP properties (id, calendarIds, isDraft, utcStart, utcEnd, useDefaultAlerts, blobId, etc.) |
| §5.4 | Per-user properties (keywords, color, freeBusyStatus, useDefaultAlerts, alerts) |
| §5.7 | iCalComponent wire property |
| §5.11.1 | CalendarEventFilterCondition |
| §5.11.2 | CalendarEventComparator |
| §5.13 | CalendarsParseCapability |
| §7 | CalendarEventNotification, Person, NotificationType |
| §7.4.1 | NotificationFilterCondition |
RFC 8984 (JSCalendar)
| Section | Topic |
|---|---|
| §1.4.5 | LocalDateTime newtype |
| §1.4.6 | Duration newtype |
| §1.4.7 | SignedDuration newtype |
| §1.4.10 | Relation |
| §1.4.11 | Link |
| §4.1 | Metadata properties (uid, relatedTo, prodId, created, updated, sequence) |
| §4.2 | What/where properties (title, description, locations, virtualLocations, links, keywords, color) |
| §4.3 | Recurrence properties (recurrenceId, recurrenceRules, recurrenceOverrides, excluded) |
| §4.3.3 | RecurrenceRule, NDay |
| §4.4 | Scheduling/sharing (priority, freeBusyStatus, privacy, participants, replyTo) |
| §4.4.6 | Participant |
| §4.5 | Alert properties (alerts) |
| §4.5.2 | Alert, AlertTrigger, OffsetTrigger, AbsoluteTrigger |
| §4.6 | Multilingual (localizations) |
| §4.7 | Time zone (timeZone, timeZones) |
| §4.7.2 | Custom time zone definitions (opaque serde_json::Value passthrough) |
| §5.1 | Event-specific (start, duration, status) |
How to use
Deserialize a CalendarEvent
use CalendarEvent;
let json = r#"{
"id": "ev1",
"uid": "abc-123@example.com",
"title": "Team meeting",
"start": "2024-06-15T10:00:00",
"duration": "PT1H",
"timeZone": "America/New_York",
"calendarIds": { "cal1": true },
"updated": "2024-06-01T00:00:00Z"
}"#;
let event: CalendarEvent = from_str.unwrap;
assert_eq!;
Deserialize a Calendar
use Calendar;
let json = r#"{
"id": "cal1",
"name": "Work",
"color": "#4285f4",
"isSubscribed": true,
"isVisible": true,
"isDefault": false,
"includeInAvailability": "all",
"myRights": {
"mayReadFreeBusy": true,
"mayReadItems": true,
"mayWriteAll": true,
"mayWriteOwn": true,
"mayUpdatePrivate": true,
"mayRSVP": true,
"mayShare": false,
"mayDelete": false
},
"sortOrder": 0
}"#;
let cal: Calendar = from_str.unwrap;
assert_eq!;
Deserialize a BusyPeriod
use BusyPeriod;
let json = r#"{
"utcStart": "2024-06-15T09:00:00Z",
"utcEnd": "2024-06-15T10:00:00Z",
"busyStatus": "busy"
}"#;
let period: BusyPeriod = from_str.unwrap;
assert_eq!;
How it works
rename_all = "camelCase"
Every struct carries #[serde(rename_all = "camelCase")]. Rust field names
use snake_case; the wire format is camelCase per JSCalendar and JMAP
conventions. The mapping is automatic: calendar_ids → "calendarIds",
recurrence_rules → "recurrenceRules", etc.
#[serde(rename = "@type")] for JSCalendar type discriminators
JSCalendar embeds an @type field in sub-objects to identify their kind
(e.g. "RecurrenceRule", "Participant", "Alert"). The @ character is
not valid in a Rust identifier, so each such field is declared as:
pub at_type: String,
AlertTrigger uses an internally-tagged serde enum (#[serde(tag = "@type")])
so that OffsetTrigger, AbsoluteTrigger, and the forward-compatibility
Unknown(serde_json::Value) variant all dispatch on the same @type field.
#[non_exhaustive] policy
All public structs and enums are #[non_exhaustive]. New fields may be added
as the draft evolves without breaking downstream crates. Construct structs
using ..Default::default() or the builder pattern your crate provides.
serde_json::Value for open-ended extension points
Several CalendarEvent fields use Option<serde_json::Value> rather than
concrete typed structs:
| Field | Why Value |
|---|---|
timeZones |
Custom VTIMEZONE definitions (RFC 8984 §4.7.2) are a complex nested format requiring a full timezone parser. Almost no caller needs to construct these. |
locations, virtualLocations, links, participants, alerts, recurrenceRules, excludedRecurrenceRules, keywords, categories, replyTo, relatedTo |
JSCalendar defines these as open-ended maps/arrays where values are rich nested objects. Concrete types for each exist in the jscalendar module; callers that need typed access deserialize the Value themselves using those types. |
The fields recurrenceOverrides and localizations use a typed envelope —
Option<BTreeMap<String, jmap_types::PatchObject>> — because they ARE
RFC 8620 §5.3 PatchObjects at the JMAP layer. The outer envelope is
typed; the inner PatchObject leaves remain serde_json::Value to
preserve per-leaf JSCalendar shape flexibility. Wire format is
byte-identical to a plain JSON object map via PatchObject's
#[serde(transparent)].
This is the correct representation, not laziness — do not change these to typed structs without reading RFC 8984 §3.3 on extensibility.
iCalComponent wire name
The ical_component field on CalendarEvent carries the raw iCalendar
representation of the event. The wire name is "iCalComponent" — not
"icalComponent" — because "iCal" is a brand abbreviation with mixed case.
The automatic rename_all = "camelCase" conversion would produce the wrong
name, so this field carries an explicit override:
pub ical_component: ,
Gotchas
-
timeZones,links,participants,alerts,locations,virtualLocationsuseOption<serde_json::Value>because JSCalendar defines these as open-ended maps where values are complex nested objects. Callers needing typed access must deserialize theValuethemselves using the concrete types in thejscalendarmodule. -
recurrenceOverrides,localizationsuseOption<BTreeMap<String, jmap_types::PatchObject>>: the outer envelope is typed (RFC 8620 §5.3 PatchObject) while inner leaves remainValuefor JSCalendar shape flexibility. Construct viaPatchObject::from_map(...)orMap::into(). -
iCalComponent: Option<String>carries raw base64-encoded iCalendar data with no validation. The crate does not parse or validate the iCalendar content; that is the consumer's responsibility. -
iCalComponentbrand-mismatch hazard. The spec wire name isiCalComponent(capital I, capital C) — reflecting the "iCal" brand spelling. A peer that follows naiverename_all = "camelCase"rules emitsicalComponent(lowercasei), which does NOT deserialize into the typedical_componentfield — it falls into the extras catch-all and round-trips back out under the same lowercase spelling. Consumers reading.ical_componentwill seeNoneand conclude no data was provided even though the bytes are present inextra["icalComponent"]. Pinned in regression testcalendar_event_naive_icalcomponent_lands_in_extras_not_typed_field. -
Draft expired. draft-ietf-jmap-calendars-26 is an expired Internet-Draft, not a published RFC. Some ambiguous field semantics use best-judgment interpretation based on the JSCalendar RFC 8984 normative base. The type definitions will need review when (if) the draft is published.
-
LocalDateTimeandDurationare unvalidated newtype wrappers aroundString. Format correctness (RFC 8984 §1.4.5, §1.4.6) is the backend's responsibility; this crate does not parse the internal format.
Crate family
jmap-types (RFC 8620 wire primitives)
└── jmap-calendars-types ← this crate
├── jmap-calendars-server (method handlers)
└── jmap-calendars-client (client extension trait)
Path dependencies between crates use path = "../crate-jmap-*" and will
remain that way until the family is published to crates.io.
References
- draft-ietf-jmap-calendars-26 — JMAP Calendars binding (Calendar, CalendarEvent additions, CalendarEventNotification, ParticipantIdentity, capability objects, error codes)
- RFC 8984 — JSCalendar Event format (all JSCalendar Event properties, RecurrenceRule, Alert, Participant, Location, etc.)
- RFC 8620 — JMAP Core (request format, PatchObject, SetError, ResultReference)