Skip to main content

io_jmap/calendars/calendar_event/
get.rs

1//! JMAP `CalendarEvent/get` coroutine (draft-ietf-jmap-calendars-27):
2//! builds its own method call, the recurrence-expansion window and the
3//! participant reduction being arguments the generic [`JmapGet`] has no
4//! notion of, then decodes the response through it.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use std::{
10//!     io::{Read, Write},
11//!     net::TcpStream,
12//! };
13//!
14//! use io_jmap::{
15//!     calendars::calendar_event::get::{JmapCalendarEventGet, JmapCalendarEventGetOptions},
16//!     coroutine::{JmapCoroutine, JmapCoroutineState, JmapYield},
17//!     rfc8620::session::JmapSession,
18//! };
19//! use secrecy::SecretString;
20//!
21//! // Ready stream needed (TCP-connected, TLS-negociated)
22//! let mut stream = TcpStream::connect("api.example.com:443").unwrap();
23//! let mut buf = [0u8; 4096];
24//!
25//! let session: JmapSession = serde_json::from_str(r#"{
26//!     "username": "",
27//!     "accounts": {},
28//!     "primaryAccounts": {"urn:ietf:params:jmap:calendars": "a1"},
29//!     "capabilities": {},
30//!     "apiUrl": "https://api.example.com/jmap/",
31//!     "downloadUrl": "",
32//!     "uploadUrl": "",
33//!     "eventSourceUrl": "",
34//!     "state": ""
35//! }"#).unwrap();
36//! let auth = SecretString::from("Bearer xyz");
37//! let opts = JmapCalendarEventGetOptions {
38//!     ids: Some(vec!["e1".into()]),
39//!     ..Default::default()
40//! };
41//! let mut coroutine = JmapCalendarEventGet::new(&session, &auth, opts).unwrap();
42//! let mut arg = None;
43//!
44//! let out = loop {
45//!     match coroutine.resume(arg.take()) {
46//!         JmapCoroutineState::Yielded(JmapYield::WantsWrite(bytes)) => {
47//!             stream.write_all(&bytes).unwrap();
48//!         }
49//!         JmapCoroutineState::Yielded(JmapYield::WantsRead) => {
50//!             let n = stream.read(&mut buf).unwrap();
51//!             arg = Some(&buf[..n]);
52//!         }
53//!         JmapCoroutineState::Complete(Ok(out)) => break out,
54//!         JmapCoroutineState::Complete(Err(err)) => panic!("{err}"),
55//!     }
56//! };
57//!
58//! println!("{} events", out.events.len());
59//! ```
60
61use alloc::{string::String, vec, vec::Vec};
62
63use secrecy::SecretString;
64use serde::Serialize;
65use thiserror::Error;
66
67use crate::{
68    calendars::{JMAP_CALENDARS_CAPABILITY, calendar_event::JmapCalendarEvent},
69    coroutine::*,
70    jmap_try,
71    rfc8620::{JMAP_CORE_CAPABILITY, get::*, request::JmapBatch, send::*, session::JmapSession},
72};
73
74/// Failure causes during a JMAP `CalendarEvent/get` flow.
75#[derive(Debug, Error)]
76pub enum JmapCalendarEventGetError {
77    /// The inner send coroutine failed.
78    #[error("JMAP CalendarEvent/get failed: {0}")]
79    Send(#[from] JmapSendError),
80    /// The method arguments could not be serialized.
81    #[error("JMAP CalendarEvent/get failed: serialize args: {0}")]
82    SerializeArgs(#[source] serde_json::Error),
83    /// The inner generic get coroutine failed.
84    #[error("JMAP CalendarEvent/get failed: {0}")]
85    Get(#[from] JmapGetError),
86}
87
88/// Options for [`JmapCalendarEventGet::new`].
89#[derive(Clone, Debug, Default)]
90pub struct JmapCalendarEventGetOptions {
91    /// Restrict the fetch to these CalendarEvent IDs; `None` fetches
92    /// all.
93    pub ids: Option<Vec<String>>,
94    /// Restrict the returned properties (JSCalendar property names plus
95    /// the JMAP ones); `None` returns all.
96    pub properties: Option<Vec<String>>,
97    /// Drop the recurrence overrides starting at or after this UTC
98    /// date-time, keeping a fetch of a long-running series small.
99    pub recurrence_overrides_before: Option<String>,
100    /// Drop the recurrence overrides ending before this UTC date-time.
101    pub recurrence_overrides_after: Option<String>,
102    /// Return only the participants the user needs to answer an
103    /// invitation, i.e. themselves and the owners.
104    pub reduce_participants: bool,
105    /// IANA time zone id the returned floating times are resolved
106    /// against; `None` leaves the server default (`Etc/UTC`).
107    pub time_zone: Option<String>,
108}
109
110/// Successful terminal output of [`JmapCalendarEventGet`].
111#[derive(Clone, Debug)]
112pub struct JmapCalendarEventGetOutput {
113    /// The fetched calendar events.
114    pub events: Vec<JmapCalendarEvent>,
115    /// The requested ids the server did not find.
116    pub not_found: Vec<String>,
117    /// The new server state after the call.
118    pub new_state: String,
119    /// Whether the server indicated the connection can be reused.
120    pub keep_alive: bool,
121}
122
123/// I/O-free coroutine for the JMAP `CalendarEvent/get` method.
124pub struct JmapCalendarEventGet {
125    state: State,
126}
127
128impl JmapCalendarEventGet {
129    /// Prepares the method call request and builds the coroutine.
130    pub fn new(
131        session: &JmapSession,
132        http_auth: &SecretString,
133        opts: JmapCalendarEventGetOptions,
134    ) -> Result<Self, JmapCalendarEventGetError> {
135        let account_id = session
136            .primary_accounts
137            .get(JMAP_CALENDARS_CAPABILITY)
138            .cloned()
139            .unwrap_or_default();
140        let api_url = &session.api_url;
141
142        let args = serde_json::to_value(CalendarEventGetArgs {
143            account_id: &account_id,
144            ids: opts.ids.as_deref(),
145            properties: opts.properties.as_deref(),
146            recurrence_overrides_before: opts.recurrence_overrides_before.as_deref(),
147            recurrence_overrides_after: opts.recurrence_overrides_after.as_deref(),
148            reduce_participants: opts.reduce_participants,
149            time_zone: opts.time_zone.as_deref(),
150        })
151        .map_err(JmapCalendarEventGetError::SerializeArgs)?;
152
153        let mut batch = JmapBatch::new();
154        batch.add("CalendarEvent/get", args);
155        let request = batch.into_request(vec![
156            JMAP_CORE_CAPABILITY.into(),
157            JMAP_CALENDARS_CAPABILITY.into(),
158        ]);
159
160        let send = JmapSend::new(http_auth, api_url, request)?;
161
162        Ok(Self {
163            state: State::Get(JmapGet::from_send(send)),
164        })
165    }
166}
167
168impl JmapCoroutine for JmapCalendarEventGet {
169    type Yield = JmapYield;
170    type Return = Result<JmapCalendarEventGetOutput, JmapCalendarEventGetError>;
171
172    fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
173        match &mut self.state {
174            State::Get(get) => {
175                let JmapGetOutput {
176                    list,
177                    not_found,
178                    state,
179                    keep_alive,
180                } = jmap_try!(get, arg);
181                JmapCoroutineState::Complete(Ok(JmapCalendarEventGetOutput {
182                    events: list,
183                    not_found,
184                    new_state: state,
185                    keep_alive,
186                }))
187            }
188        }
189    }
190}
191
192enum State {
193    Get(JmapGet<JmapCalendarEvent>),
194}
195
196#[derive(Serialize)]
197#[serde(rename_all = "camelCase")]
198struct CalendarEventGetArgs<'a> {
199    account_id: &'a str,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    ids: Option<&'a [String]>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    properties: Option<&'a [String]>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    recurrence_overrides_before: Option<&'a str>,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    recurrence_overrides_after: Option<&'a str>,
208    #[serde(skip_serializing_if = "is_false")]
209    reduce_participants: bool,
210    #[serde(skip_serializing_if = "Option::is_none")]
211    time_zone: Option<&'a str>,
212}
213
214fn is_false(b: &bool) -> bool {
215    !b
216}