use core::fmt;
use alloc::{string::String, vec, vec::Vec};
use secrecy::SecretString;
use serde::{Deserialize, Serialize, Serializer};
use thiserror::Error;
use crate::{
calendars::{JMAP_CALENDARS_CAPABILITY, calendar_event::JmapCalendarEvent},
coroutine::*,
jmap_try,
rfc8620::{
JMAP_CORE_CAPABILITY,
error::JmapMethodError,
request::{JmapBatch, JmapResultReference},
send::*,
session::JmapSession,
},
};
#[derive(Clone, Debug, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JmapCalendarEventFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub in_calendar: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attendee: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uid: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum JmapCalendarEventSortProperty {
Start,
Uid,
RecurrenceId,
Created,
Updated,
}
impl fmt::Display for JmapCalendarEventSortProperty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Start => "start",
Self::Uid => "uid",
Self::RecurrenceId => "recurrenceId",
Self::Created => "created",
Self::Updated => "updated",
})
}
}
impl Serialize for JmapCalendarEventSortProperty {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.collect_str(self)
}
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JmapCalendarEventSortComparator {
pub property: JmapCalendarEventSortProperty,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_ascending: Option<bool>,
}
#[derive(Debug, Error)]
pub enum JmapCalendarEventQueryError {
#[error(
"JMAP CalendarEvent/query failed: missing CalendarEvent/query response in method_responses"
)]
MissingQueryResponse,
#[error(
"JMAP CalendarEvent/query failed: missing CalendarEvent/get response in method_responses"
)]
MissingGetResponse,
#[error("JMAP CalendarEvent/query failed: {0}")]
Send(#[from] JmapSendError),
#[error("JMAP CalendarEvent/query failed: serialize args: {0}")]
SerializeArgs(#[source] serde_json::Error),
#[error("JMAP CalendarEvent/query failed: parse CalendarEvent/query response: {0}")]
ParseQueryResponse(#[source] serde_json::Error),
#[error("JMAP CalendarEvent/query failed: parse CalendarEvent/get response: {0}")]
ParseGetResponse(#[source] serde_json::Error),
#[error("JMAP CalendarEvent/query failed: CalendarEvent/query: {0}")]
QueryMethod(JmapMethodError),
#[error("JMAP CalendarEvent/query failed: CalendarEvent/get: {0}")]
GetMethod(JmapMethodError),
}
#[derive(Clone, Debug, Default)]
pub struct JmapCalendarEventQueryOptions {
pub filter: Option<JmapCalendarEventFilter>,
pub sort: Option<Vec<JmapCalendarEventSortComparator>>,
pub position: Option<u64>,
pub limit: Option<u64>,
pub expand_recurrences: bool,
pub time_zone: Option<String>,
pub properties: Option<Vec<String>>,
pub reduce_participants: bool,
}
#[derive(Clone, Debug)]
pub struct JmapCalendarEventQueryOutput {
pub events: Vec<JmapCalendarEvent>,
pub total: Option<u64>,
pub position: u64,
pub query_state: String,
pub keep_alive: bool,
}
pub struct JmapCalendarEventQuery {
state: State,
}
impl JmapCalendarEventQuery {
pub fn new(
session: &JmapSession,
http_auth: &SecretString,
opts: JmapCalendarEventQueryOptions,
) -> Result<Self, JmapCalendarEventQueryError> {
let account_id = session
.primary_accounts
.get(JMAP_CALENDARS_CAPABILITY)
.cloned()
.unwrap_or_default();
let api_url = &session.api_url;
let query_args = CalendarEventQueryArgs {
account_id: &account_id,
filter: opts.filter.as_ref(),
sort: opts.sort.as_deref(),
position: opts.position,
limit: opts.limit,
calculate_total: true,
expand_recurrences: opts.expand_recurrences,
time_zone: opts.time_zone.as_deref(),
};
let mut batch = JmapBatch::new();
let query_id = batch.add(
"CalendarEvent/query",
serde_json::to_value(&query_args)
.map_err(JmapCalendarEventQueryError::SerializeArgs)?,
);
let get_args = CalendarEventGetByRefArgs {
account_id: &account_id,
ids_ref: JmapResultReference {
result_of: &query_id,
name: "CalendarEvent/query",
path: "/ids",
},
properties: opts.properties.as_deref(),
reduce_participants: opts.reduce_participants,
time_zone: opts.time_zone.as_deref(),
};
batch.add(
"CalendarEvent/get",
serde_json::to_value(&get_args).map_err(JmapCalendarEventQueryError::SerializeArgs)?,
);
let request = batch.into_request(vec![
JMAP_CORE_CAPABILITY.into(),
JMAP_CALENDARS_CAPABILITY.into(),
]);
Ok(Self {
state: State::Send(JmapSend::new(http_auth, api_url, request)?),
})
}
}
impl JmapCoroutine for JmapCalendarEventQuery {
type Yield = JmapYield;
type Return = Result<JmapCalendarEventQueryOutput, JmapCalendarEventQueryError>;
fn resume(&mut self, arg: Option<&[u8]>) -> JmapCoroutineState<Self::Yield, Self::Return> {
match &mut self.state {
State::Send(send) => {
let JmapSendOutput {
response,
keep_alive,
} = jmap_try!(send, arg);
let mut responses = response.method_responses.into_iter();
let Some((query_name, query_args, _)) = responses.next() else {
return JmapCoroutineState::Complete(Err(
JmapCalendarEventQueryError::MissingQueryResponse,
));
};
if query_name == "error" {
let err = serde_json::from_value::<JmapMethodError>(query_args)
.unwrap_or(JmapMethodError::Unknown);
return JmapCoroutineState::Complete(Err(
JmapCalendarEventQueryError::QueryMethod(err),
));
}
let query_response =
match serde_json::from_value::<CalendarEventQueryResponse>(query_args) {
Ok(r) => r,
Err(err) => {
return JmapCoroutineState::Complete(Err(
JmapCalendarEventQueryError::ParseQueryResponse(err),
));
}
};
let Some((get_name, get_args, _)) = responses.next() else {
return JmapCoroutineState::Complete(Err(
JmapCalendarEventQueryError::MissingGetResponse,
));
};
if get_name == "error" {
let err = serde_json::from_value::<JmapMethodError>(get_args)
.unwrap_or(JmapMethodError::Unknown);
return JmapCoroutineState::Complete(Err(
JmapCalendarEventQueryError::GetMethod(err),
));
}
match serde_json::from_value::<CalendarEventGetResponse>(get_args) {
Ok(r) => JmapCoroutineState::Complete(Ok(JmapCalendarEventQueryOutput {
events: r.list,
total: query_response.total,
position: query_response.position,
query_state: query_response.query_state,
keep_alive,
})),
Err(err) => JmapCoroutineState::Complete(Err(
JmapCalendarEventQueryError::ParseGetResponse(err),
)),
}
}
}
}
}
enum State {
Send(JmapSend),
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct CalendarEventQueryArgs<'a> {
account_id: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
filter: Option<&'a JmapCalendarEventFilter>,
#[serde(skip_serializing_if = "Option::is_none")]
sort: Option<&'a [JmapCalendarEventSortComparator]>,
#[serde(skip_serializing_if = "Option::is_none")]
position: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
limit: Option<u64>,
calculate_total: bool,
#[serde(skip_serializing_if = "is_false")]
expand_recurrences: bool,
#[serde(skip_serializing_if = "Option::is_none")]
time_zone: Option<&'a str>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct CalendarEventGetByRefArgs<'a> {
account_id: &'a str,
#[serde(rename = "#ids")]
ids_ref: JmapResultReference<'a>,
#[serde(skip_serializing_if = "Option::is_none")]
properties: Option<&'a [String]>,
#[serde(skip_serializing_if = "is_false")]
reduce_participants: bool,
#[serde(skip_serializing_if = "Option::is_none")]
time_zone: Option<&'a str>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct CalendarEventQueryResponse {
query_state: String,
#[serde(default)]
total: Option<u64>,
#[serde(default)]
position: u64,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct CalendarEventGetResponse {
list: Vec<JmapCalendarEvent>,
}
fn is_false(b: &bool) -> bool {
!b
}