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}