io_jmap/calendars/calendar_event/
query.rs1use core::fmt;
72
73use alloc::{string::String, vec, vec::Vec};
74
75use secrecy::SecretString;
76use serde::{Deserialize, Serialize, Serializer};
77use thiserror::Error;
78
79use crate::{
80 calendars::{JMAP_CALENDARS_CAPABILITY, calendar_event::JmapCalendarEvent},
81 coroutine::*,
82 jmap_try,
83 rfc8620::{
84 JMAP_CORE_CAPABILITY,
85 error::JmapMethodError,
86 request::{JmapBatch, JmapResultReference},
87 send::*,
88 session::JmapSession,
89 },
90};
91
92#[derive(Clone, Debug, Default, Serialize)]
95#[serde(rename_all = "camelCase")]
96pub struct JmapCalendarEventFilter {
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub in_calendar: Option<String>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub after: Option<String>,
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub before: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub text: Option<String>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub title: Option<String>,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub description: Option<String>,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub location: Option<String>,
118 #[serde(skip_serializing_if = "Option::is_none")]
121 pub owner: Option<String>,
122 #[serde(skip_serializing_if = "Option::is_none")]
125 pub attendee: Option<String>,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub uid: Option<String>,
129}
130
131#[derive(Clone, Debug, PartialEq, Eq)]
133pub enum JmapCalendarEventSortProperty {
134 Start,
136 Uid,
138 RecurrenceId,
141 Created,
143 Updated,
145}
146
147impl fmt::Display for JmapCalendarEventSortProperty {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 f.write_str(match self {
150 Self::Start => "start",
151 Self::Uid => "uid",
152 Self::RecurrenceId => "recurrenceId",
153 Self::Created => "created",
154 Self::Updated => "updated",
155 })
156 }
157}
158
159impl Serialize for JmapCalendarEventSortProperty {
160 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
161 s.collect_str(self)
162 }
163}
164
165#[derive(Clone, Debug, Serialize)]
167#[serde(rename_all = "camelCase")]
168pub struct JmapCalendarEventSortComparator {
169 pub property: JmapCalendarEventSortProperty,
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub is_ascending: Option<bool>,
174}
175
176#[derive(Debug, Error)]
179pub enum JmapCalendarEventQueryError {
180 #[error(
182 "JMAP CalendarEvent/query failed: missing CalendarEvent/query response in method_responses"
183 )]
184 MissingQueryResponse,
185 #[error(
187 "JMAP CalendarEvent/query failed: missing CalendarEvent/get response in method_responses"
188 )]
189 MissingGetResponse,
190 #[error("JMAP CalendarEvent/query failed: {0}")]
192 Send(#[from] JmapSendError),
193 #[error("JMAP CalendarEvent/query failed: serialize args: {0}")]
195 SerializeArgs(#[source] serde_json::Error),
196 #[error("JMAP CalendarEvent/query failed: parse CalendarEvent/query response: {0}")]
198 ParseQueryResponse(#[source] serde_json::Error),
199 #[error("JMAP CalendarEvent/query failed: parse CalendarEvent/get response: {0}")]
201 ParseGetResponse(#[source] serde_json::Error),
202 #[error("JMAP CalendarEvent/query failed: CalendarEvent/query: {0}")]
204 QueryMethod(JmapMethodError),
205 #[error("JMAP CalendarEvent/query failed: CalendarEvent/get: {0}")]
207 GetMethod(JmapMethodError),
208}
209
210#[derive(Clone, Debug, Default)]
212pub struct JmapCalendarEventQueryOptions {
213 pub filter: Option<JmapCalendarEventFilter>,
215 pub sort: Option<Vec<JmapCalendarEventSortComparator>>,
217 pub position: Option<u64>,
219 pub limit: Option<u64>,
221 pub expand_recurrences: bool,
225 pub time_zone: Option<String>,
229 pub properties: Option<Vec<String>>,
232 pub reduce_participants: bool,
235}
236
237#[derive(Clone, Debug)]
239pub struct JmapCalendarEventQueryOutput {
240 pub events: Vec<JmapCalendarEvent>,
242 pub total: Option<u64>,
245 pub position: u64,
247 pub query_state: String,
249 pub keep_alive: bool,
251}
252
253pub struct JmapCalendarEventQuery {
256 state: State,
257}
258
259impl JmapCalendarEventQuery {
260 pub fn new(
262 session: &JmapSession,
263 http_auth: &SecretString,
264 opts: JmapCalendarEventQueryOptions,
265 ) -> Result<Self, JmapCalendarEventQueryError> {
266 let account_id = session
267 .primary_accounts
268 .get(JMAP_CALENDARS_CAPABILITY)
269 .cloned()
270 .unwrap_or_default();
271 let api_url = &session.api_url;
272
273 let query_args = CalendarEventQueryArgs {
274 account_id: &account_id,
275 filter: opts.filter.as_ref(),
276 sort: opts.sort.as_deref(),
277 position: opts.position,
278 limit: opts.limit,
279 calculate_total: true,
280 expand_recurrences: opts.expand_recurrences,
281 time_zone: opts.time_zone.as_deref(),
282 };
283
284 let mut batch = JmapBatch::new();
285 let query_id = batch.add(
286 "CalendarEvent/query",
287 serde_json::to_value(&query_args)
288 .map_err(JmapCalendarEventQueryError::SerializeArgs)?,
289 );
290
291 let get_args = CalendarEventGetByRefArgs {
292 account_id: &account_id,
293 ids_ref: JmapResultReference {
294 result_of: &query_id,
295 name: "CalendarEvent/query",
296 path: "/ids",
297 },
298 properties: opts.properties.as_deref(),
299 reduce_participants: opts.reduce_participants,
300 time_zone: opts.time_zone.as_deref(),
301 };
302
303 batch.add(
304 "CalendarEvent/get",
305 serde_json::to_value(&get_args).map_err(JmapCalendarEventQueryError::SerializeArgs)?,
306 );
307
308 let request = batch.into_request(vec![
309 JMAP_CORE_CAPABILITY.into(),
310 JMAP_CALENDARS_CAPABILITY.into(),
311 ]);
312
313 Ok(Self {
314 state: State::Send(JmapSend::new(http_auth, api_url, request)?),
315 })
316 }
317}
318
319impl JmapCoroutine for JmapCalendarEventQuery {
320 type Yield = JmapYield;
321 type Return = Result<JmapCalendarEventQueryOutput, JmapCalendarEventQueryError>;
322
323 fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
324 match &mut self.state {
325 State::Send(send) => {
326 let JmapSendOutput {
327 response,
328 keep_alive,
329 } = jmap_try!(send, arg);
330
331 let mut responses = response.method_responses.into_iter();
332
333 let Some((query_name, query_args, _)) = responses.next() else {
334 return JmapCoroutineState::Complete(Err(
335 JmapCalendarEventQueryError::MissingQueryResponse,
336 ));
337 };
338
339 if query_name == "error" {
340 let err = serde_json::from_value::<JmapMethodError>(query_args)
341 .unwrap_or(JmapMethodError::Unknown);
342 return JmapCoroutineState::Complete(Err(
343 JmapCalendarEventQueryError::QueryMethod(err),
344 ));
345 }
346
347 let query_response =
348 match serde_json::from_value::<CalendarEventQueryResponse>(query_args) {
349 Ok(r) => r,
350 Err(err) => {
351 return JmapCoroutineState::Complete(Err(
352 JmapCalendarEventQueryError::ParseQueryResponse(err),
353 ));
354 }
355 };
356
357 let Some((get_name, get_args, _)) = responses.next() else {
358 return JmapCoroutineState::Complete(Err(
359 JmapCalendarEventQueryError::MissingGetResponse,
360 ));
361 };
362
363 if get_name == "error" {
364 let err = serde_json::from_value::<JmapMethodError>(get_args)
365 .unwrap_or(JmapMethodError::Unknown);
366 return JmapCoroutineState::Complete(Err(
367 JmapCalendarEventQueryError::GetMethod(err),
368 ));
369 }
370
371 match serde_json::from_value::<CalendarEventGetResponse>(get_args) {
372 Ok(r) => JmapCoroutineState::Complete(Ok(JmapCalendarEventQueryOutput {
373 events: r.list,
374 total: query_response.total,
375 position: query_response.position,
376 query_state: query_response.query_state,
377 keep_alive,
378 })),
379 Err(err) => JmapCoroutineState::Complete(Err(
380 JmapCalendarEventQueryError::ParseGetResponse(err),
381 )),
382 }
383 }
384 }
385 }
386}
387
388enum State {
389 Send(JmapSend),
390}
391
392#[derive(Serialize)]
393#[serde(rename_all = "camelCase")]
394struct CalendarEventQueryArgs<'a> {
395 account_id: &'a str,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 filter: Option<&'a JmapCalendarEventFilter>,
398 #[serde(skip_serializing_if = "Option::is_none")]
399 sort: Option<&'a [JmapCalendarEventSortComparator]>,
400 #[serde(skip_serializing_if = "Option::is_none")]
401 position: Option<u64>,
402 #[serde(skip_serializing_if = "Option::is_none")]
403 limit: Option<u64>,
404 calculate_total: bool,
405 #[serde(skip_serializing_if = "is_false")]
406 expand_recurrences: bool,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 time_zone: Option<&'a str>,
409}
410
411#[derive(Serialize)]
412#[serde(rename_all = "camelCase")]
413struct CalendarEventGetByRefArgs<'a> {
414 account_id: &'a str,
415 #[serde(rename = "#ids")]
416 ids_ref: JmapResultReference<'a>,
417 #[serde(skip_serializing_if = "Option::is_none")]
418 properties: Option<&'a [String]>,
419 #[serde(skip_serializing_if = "is_false")]
420 reduce_participants: bool,
421 #[serde(skip_serializing_if = "Option::is_none")]
422 time_zone: Option<&'a str>,
423}
424
425#[derive(Deserialize)]
426#[serde(rename_all = "camelCase")]
427struct CalendarEventQueryResponse {
428 query_state: String,
429 #[serde(default)]
430 total: Option<u64>,
431 #[serde(default)]
432 position: u64,
433}
434
435#[derive(Deserialize)]
436#[serde(rename_all = "camelCase")]
437struct CalendarEventGetResponse {
438 list: Vec<JmapCalendarEvent>,
439}
440
441fn is_false(b: &bool) -> bool {
442 !b
443}