use core::fmt;
use alloc::{string::String, vec, vec::Vec};
use secrecy::SecretString;
use serde::{Deserialize, Serialize, Serializer};
use thiserror::Error;
use crate::{
coroutine::*,
jmap_try,
rfc8620::{
JMAP_CORE_CAPABILITY, error::JmapMethodError, request::JmapBatch,
request::JmapResultReference, send::*, session::JmapSession,
},
rfc9610::{JMAP_CONTACTS_CAPABILITY, contact_card::JmapContactCard},
};
#[derive(Clone, Debug, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JmapContactCardFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub in_address_book: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_member: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_after: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_after: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "name/given", skip_serializing_if = "Option::is_none")]
pub name_given: Option<String>,
#[serde(rename = "name/surname", skip_serializing_if = "Option::is_none")]
pub name_surname: Option<String>,
#[serde(rename = "name/surname2", skip_serializing_if = "Option::is_none")]
pub name_surname2: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nickname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub organization: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub online_service: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum JmapContactCardSortProperty {
Created,
Updated,
NameGiven,
NameSurname,
NameSurname2,
}
impl fmt::Display for JmapContactCardSortProperty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Created => "created",
Self::Updated => "updated",
Self::NameGiven => "name/given",
Self::NameSurname => "name/surname",
Self::NameSurname2 => "name/surname2",
})
}
}
impl Serialize for JmapContactCardSortProperty {
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 JmapContactCardSortComparator {
pub property: JmapContactCardSortProperty,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_ascending: Option<bool>,
}
#[derive(Debug, Error)]
pub enum JmapContactCardQueryError {
#[error(
"JMAP ContactCard/query failed: missing ContactCard/query response in method_responses"
)]
MissingQueryResponse,
#[error("JMAP ContactCard/query failed: missing ContactCard/get response in method_responses")]
MissingGetResponse,
#[error("JMAP ContactCard/query failed: {0}")]
Send(#[from] JmapSendError),
#[error("JMAP ContactCard/query failed: serialize args: {0}")]
SerializeArgs(#[source] serde_json::Error),
#[error("JMAP ContactCard/query failed: parse ContactCard/query response: {0}")]
ParseQueryResponse(#[source] serde_json::Error),
#[error("JMAP ContactCard/query failed: parse ContactCard/get response: {0}")]
ParseGetResponse(#[source] serde_json::Error),
#[error("JMAP ContactCard/query failed: ContactCard/query: {0}")]
QueryMethod(JmapMethodError),
#[error("JMAP ContactCard/query failed: ContactCard/get: {0}")]
GetMethod(JmapMethodError),
}
#[derive(Clone, Debug, Default)]
pub struct JmapContactCardQueryOptions {
pub filter: Option<JmapContactCardFilter>,
pub sort: Option<Vec<JmapContactCardSortComparator>>,
pub position: Option<u64>,
pub limit: Option<u64>,
pub properties: Option<Vec<String>>,
}
#[derive(Clone, Debug)]
pub struct JmapContactCardQueryOutput {
pub cards: Vec<JmapContactCard>,
pub total: Option<u64>,
pub position: u64,
pub query_state: String,
pub keep_alive: bool,
}
pub struct JmapContactCardQuery {
state: State,
}
impl JmapContactCardQuery {
pub fn new(
session: &JmapSession,
http_auth: &SecretString,
opts: JmapContactCardQueryOptions,
) -> Result<Self, JmapContactCardQueryError> {
let account_id = session
.primary_accounts
.get(JMAP_CONTACTS_CAPABILITY)
.cloned()
.unwrap_or_default();
let api_url = &session.api_url;
let query_args = ContactCardQueryArgs {
account_id: &account_id,
filter: opts.filter.as_ref(),
sort: opts.sort.as_deref(),
position: opts.position,
limit: opts.limit,
calculate_total: true,
};
let mut batch = JmapBatch::new();
let query_id = batch.add(
"ContactCard/query",
serde_json::to_value(&query_args).map_err(JmapContactCardQueryError::SerializeArgs)?,
);
let get_args = ContactCardGetByRefArgs {
account_id: &account_id,
ids_ref: JmapResultReference {
result_of: &query_id,
name: "ContactCard/query",
path: "/ids",
},
properties: opts.properties.as_deref(),
};
batch.add(
"ContactCard/get",
serde_json::to_value(&get_args).map_err(JmapContactCardQueryError::SerializeArgs)?,
);
let request = batch.into_request(vec![
JMAP_CORE_CAPABILITY.into(),
JMAP_CONTACTS_CAPABILITY.into(),
]);
Ok(Self {
state: State::Send(JmapSend::new(http_auth, api_url, request)?),
})
}
}
impl JmapCoroutine for JmapContactCardQuery {
type Yield = JmapYield;
type Return = Result<JmapContactCardQueryOutput, JmapContactCardQueryError>;
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(
JmapContactCardQueryError::MissingQueryResponse,
));
};
if query_name == "error" {
let err = serde_json::from_value::<JmapMethodError>(query_args)
.unwrap_or(JmapMethodError::Unknown);
return JmapCoroutineState::Complete(Err(
JmapContactCardQueryError::QueryMethod(err),
));
}
let query_response =
match serde_json::from_value::<ContactCardQueryResponse>(query_args) {
Ok(r) => r,
Err(err) => {
return JmapCoroutineState::Complete(Err(
JmapContactCardQueryError::ParseQueryResponse(err),
));
}
};
let Some((get_name, get_args, _)) = responses.next() else {
return JmapCoroutineState::Complete(Err(
JmapContactCardQueryError::MissingGetResponse,
));
};
if get_name == "error" {
let err = serde_json::from_value::<JmapMethodError>(get_args)
.unwrap_or(JmapMethodError::Unknown);
return JmapCoroutineState::Complete(Err(
JmapContactCardQueryError::GetMethod(err),
));
}
match serde_json::from_value::<ContactCardGetResponse>(get_args) {
Ok(r) => JmapCoroutineState::Complete(Ok(JmapContactCardQueryOutput {
cards: r.list,
total: query_response.total,
position: query_response.position,
query_state: query_response.query_state,
keep_alive,
})),
Err(err) => JmapCoroutineState::Complete(Err(
JmapContactCardQueryError::ParseGetResponse(err),
)),
}
}
}
}
}
enum State {
Send(JmapSend),
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ContactCardQueryArgs<'a> {
account_id: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
filter: Option<&'a JmapContactCardFilter>,
#[serde(skip_serializing_if = "Option::is_none")]
sort: Option<&'a [JmapContactCardSortComparator]>,
#[serde(skip_serializing_if = "Option::is_none")]
position: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
limit: Option<u64>,
calculate_total: bool,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ContactCardGetByRefArgs<'a> {
account_id: &'a str,
#[serde(rename = "#ids")]
ids_ref: JmapResultReference<'a>,
#[serde(skip_serializing_if = "Option::is_none")]
properties: Option<&'a [String]>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct ContactCardQueryResponse {
query_state: String,
#[serde(default)]
total: Option<u64>,
#[serde(default)]
position: u64,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct ContactCardGetResponse {
list: Vec<JmapContactCard>,
}