Skip to main content

jmap_calendars_types/
availability.rs

1//! BusyPeriod and related types for `Principal/getAvailability`.
2//!
3//! Normative reference: draft-ietf-jmap-calendars-33 §2.2.
4
5use jmap_types::UTCDate;
6use serde::{Deserialize, Serialize};
7
8/// The `busyStatus` of a [`BusyPeriod`] (draft-ietf-jmap-calendars-33
9/// §2.2): `String (default: "unavailable")` that "MUST be one of" the
10/// three values below, so any other string fails to deserialize.
11#[non_exhaustive]
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum BusyStatus {
15    /// `"confirmed"` — "The event status is "confirmed" and the
16    /// Principal's "participationStatus" is "accepted"."
17    Confirmed,
18    /// `"tentative"` — "The event status is "tentative" or the
19    /// Principal's "participationStatus" is "tentative"."
20    Tentative,
21    /// `"unavailable"` — "The Principal is not available for scheduling
22    /// at this time for any other reason." The default.
23    #[default]
24    Unavailable,
25}
26
27/// A single busy period returned by `Principal/getAvailability`
28/// (draft-ietf-jmap-calendars-33 §2.2).
29///
30/// The server returns an array of `BusyPeriod` objects representing time ranges
31/// when the principal is occupied.  If `showDetails` was `false` in the request
32/// (or the calendar has `privacy:"secret"`, or the requesting user lacks
33/// `mayReadItems` rights), the `event` and `account_id` fields are `null`.
34///
35/// ## Field semantics
36///
37/// - `utc_start` / `utc_end` — inclusive/exclusive UTC date-time bounds of the
38///   busy period; both are always present.
39/// - `busy_status` — the kind of busy time; [`BusyStatus::Unavailable`]
40///   when absent on the wire.
41/// - `event` — the underlying [`crate::CalendarEvent`] if the requesting user
42///   has the right to see it, otherwise `null`.
43/// - `account_id` — the JMAP account id of the calendar containing this event;
44///   `null` when `event` is `null`.
45#[non_exhaustive]
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub struct BusyPeriod {
49    /// UTC start of the busy period (inclusive).
50    ///
51    /// Uses the [`UTCDate`] newtype (RFC 8620 §1.4) rather than a bare
52    /// `String` to make the wire-format constraint
53    /// (20-character `YYYY-MM-DDTHH:MM:SSZ`) explicit at the type level.
54    /// JSON serialization is unchanged because `UTCDate` is a transparent
55    /// newtype.
56    pub utc_start: UTCDate,
57
58    /// UTC end of the busy period (exclusive).
59    ///
60    /// See [`utc_start`](Self::utc_start) for the typing rationale.
61    pub utc_end: UTCDate,
62
63    /// Characterisation of the busy time; an absent `busyStatus`
64    /// deserializes as [`BusyStatus::Unavailable`], the draft's default.
65    #[serde(default)]
66    pub busy_status: BusyStatus,
67
68    /// The underlying event, if the requester has read access.
69    ///
70    /// Represented as a raw JSON value because the server emits a
71    /// server-side projection of the [`crate::CalendarEvent`] rather
72    /// than the full object:
73    ///
74    /// - draft-ietf-jmap-calendars-33 §2.2 (the `Principal/getAvailability`
75    ///   `eventProperties` argument) restricts the returned representation
76    ///   to a caller-requested property subset.
77    /// - The server also enforces privacy and `mayReadItems` permission
78    ///   filtering on a per-event basis (the spec lists the conditions
79    ///   under which `event` is `null`).
80    ///
81    /// Modelling that projection at the type level would require either
82    /// a typed partial-`CalendarEvent` shape (large, tracks the spec) or
83    /// `Option<crate::CalendarEvent>` where every field is `Option` by
84    /// workspace convention. Using `serde_json::Value` keeps the
85    /// projection nature explicit on the wire, avoids forcing
86    /// availability consumers to walk the full `CalendarEvent` deserialize
87    /// path, and lets callers that need typed access deserialize the
88    /// value into [`crate::CalendarEvent`] on demand.
89    ///
90    /// (The previous version of this docstring cited a "circular type
91    /// dependency" between `BusyPeriod` and `CalendarEvent`. That was
92    /// inaccurate — both live in the same crate as sibling modules and
93    /// neither depends on the other.)
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub event: Option<serde_json::Value>,
96
97    /// JMAP account id of the calendar containing this event.
98    ///
99    /// `null` (i.e., `None`) when `event` is `null`.
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub account_id: Option<jmap_types::Id>,
102
103    /// Catch-all for vendor / site / private extension fields not covered
104    /// by the typed fields above. Preserves unknown fields across
105    /// deserialize/serialize round-trip per workspace extras-preservation
106    /// policy (see workspace AGENTS.md).
107    #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
108    pub extra: serde_json::Map<String, serde_json::Value>,
109}