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}