jmap-calendars-types 0.3.0

JMAP Calendars extension data types (draft-ietf-jmap-calendars-*)
Documentation

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 URI urn:ietf:params:jmap:metadata), (draft-ietf-jmap-metadata-02), which adds namespace-keyed metadata / privateMetadata properties to each opted-in data type, with capability-declared schema (dataTypes → namespaces / supportsVendorNamespaces / maxDepth, §1.2.1) and metadataExists / metadataTextContains / metadataTextEquals conditions on the type's own /query filter (§3.5). This is the workspace's recommended path for vendor data that needs to be queryable. Helpers live in jmap-metadata-types, jmap-metadata-server, and jmap-metadata-client.
  • Pre-IETF escape. If you cannot wait for the metadata draft, escape the filter tree to serde_json::Value or fork the FilterCondition types. See PLAN.md in 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 jmap_calendars_types::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 = serde_json::from_str(json).unwrap();
assert_eq!(event.title.as_deref(), Some("Team meeting"));

Deserialize a Calendar

use jmap_calendars_types::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 = serde_json::from_str(json).unwrap();
assert_eq!(cal.name, "Work");

Deserialize a BusyPeriod

use jmap_calendars_types::BusyPeriod;

let json = r#"{
    "utcStart": "2024-06-15T09:00:00Z",
    "utcEnd": "2024-06-15T10:00:00Z",
    "busyStatus": "busy"
}"#;

let period: BusyPeriod = serde_json::from_str(json).unwrap();
assert_eq!(period.utc_start, "2024-06-15T09:00:00Z");

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:

#[serde(rename = "@type")]
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:

#[serde(rename = "iCalComponent", skip_serializing_if = "Option::is_none")]
pub ical_component: Option<String>,

Gotchas

  • timeZones, links, participants, alerts, locations, virtualLocations use Option<serde_json::Value> because JSCalendar defines these as open-ended maps where values are complex nested objects. Callers needing typed access must deserialize the Value themselves using the concrete types in the jscalendar module.

  • recurrenceOverrides, localizations use Option<BTreeMap<String, jmap_types::PatchObject>>: the outer envelope is typed (RFC 8620 §5.3 PatchObject) while inner leaves remain Value for JSCalendar shape flexibility. Construct via PatchObject::from_map(...) or Map::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.

  • iCalComponent brand-mismatch hazard. The spec wire name is iCalComponent (capital I, capital C) — reflecting the "iCal" brand spelling. A peer that follows naive rename_all = "camelCase" rules emits icalComponent (lowercase i), which does NOT deserialize into the typed ical_component field — it falls into the extras catch-all and round-trips back out under the same lowercase spelling. Consumers reading .ical_component will see None and conclude no data was provided even though the bytes are present in extra["icalComponent"]. Pinned in regression test calendar_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.

  • LocalDateTime and Duration are unvalidated newtype wrappers around String. 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)