forked from meli/meli
1
Fork 0

melib/jmap: respect max_objects_in_get when fetching email

jmap-batch-fetch
Manos Pitsidianakis 2022-10-15 18:10:07 +03:00
parent 88a1f0d4bc
commit e64f2077a8
9 changed files with 285 additions and 172 deletions

View File

@ -72,6 +72,9 @@ macro_rules! _impl {
} }
} }
pub const JMAP_CORE_CAPABILITY: &str = "urn:ietf:params:jmap:core";
pub const JMAP_MAIL_CAPABILITY: &str = "urn:ietf:params:jmap:mail";
pub mod operations; pub mod operations;
use operations::*; use operations::*;
@ -196,6 +199,7 @@ pub struct Store {
pub mailbox_state: Arc<Mutex<State<MailboxObject>>>, pub mailbox_state: Arc<Mutex<State<MailboxObject>>>,
pub online_status: Arc<FutureMutex<(Instant, Result<()>)>>, pub online_status: Arc<FutureMutex<(Instant, Result<()>)>>,
pub is_subscribed: Arc<IsSubscribedFn>, pub is_subscribed: Arc<IsSubscribedFn>,
pub core_capabilities: Arc<Mutex<rfc8620::CapabilitiesObject>>,
pub event_consumer: BackendEventConsumer, pub event_consumer: BackendEventConsumer,
} }
@ -330,15 +334,19 @@ impl MailBackend for JmapType {
Ok(Box::pin(async_stream::try_stream! { Ok(Box::pin(async_stream::try_stream! {
let mut conn = connection.lock().await; let mut conn = connection.lock().await;
conn.connect().await?; conn.connect().await?;
let res = protocol::fetch( let batch_size: u64 = conn.store.core_capabilities.lock().unwrap().max_objects_in_get;
&conn, let mut fetch_state = protocol::EmailFetchState::Start { batch_size };
&store, loop {
mailbox_hash, let res = fetch_state.fetch(
).await?; &conn,
if res.is_empty() { &store,
return; mailbox_hash,
).await?;
if res.is_empty() {
return;
}
yield res;
} }
yield res;
})) }))
} }
@ -889,7 +897,7 @@ impl JmapType {
event_consumer, event_consumer,
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)), is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
collection: Collection::default(), collection: Collection::default(),
core_capabilities: Arc::new(Mutex::new(rfc8620::CapabilitiesObject::default())),
byte_cache: Default::default(), byte_cache: Default::default(),
id_store: Default::default(), id_store: Default::default(),
reverse_id_store: Default::default(), reverse_id_store: Default::default(),

View File

@ -88,19 +88,15 @@ impl JmapConnection {
} }
Ok(s) => s, Ok(s) => s,
}; };
if !session if !session.capabilities.contains_key(JMAP_CORE_CAPABILITY) {
.capabilities let err = MeliError::new(format!("Server {} did not return JMAP Core capability ({core_capability}). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", "), core_capability=JMAP_CORE_CAPABILITY));
.contains_key("urn:ietf:params:jmap:core")
{
let err = MeliError::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", ")));
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
if !session *self.store.core_capabilities.lock().unwrap() =
.capabilities session.capabilities["urn:ietf:params:jmap:core"].clone();
.contains_key("urn:ietf:params:jmap:mail") if !session.capabilities.contains_key(JMAP_MAIL_CAPABILITY) {
{ let err = MeliError::new(format!("Server {} does not support JMAP Mail capability ({mail_capability}). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", "), mail_capability=JMAP_MAIL_CAPABILITY));
let err = MeliError::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", ")));
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err); return Err(err);
} }
@ -111,7 +107,7 @@ impl JmapConnection {
} }
pub fn mail_account_id(&self) -> Id<Account> { pub fn mail_account_id(&self) -> Id<Account> {
self.session.lock().unwrap().primary_accounts["urn:ietf:params:jmap:mail"].clone() self.session.lock().unwrap().primary_accounts[JMAP_MAIL_CAPABILITY].clone()
} }
pub fn session_guard(&'_ self) -> MutexGuard<'_, JmapSession> { pub fn session_guard(&'_ self) -> MutexGuard<'_, JmapSession> {
@ -359,4 +355,29 @@ impl JmapConnection {
Ok(()) Ok(())
} }
pub async fn send_request(&self, request: String) -> Result<String> {
let api_url = self.session.lock().unwrap().api_url.clone();
let mut res = self.client.post_async(api_url.as_str(), request).await?;
let res_text = res.text().await?;
debug!(&res_text);
let _: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = MeliError::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &self.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
crate::log(err.to_string(), crate::ERROR);
crate::log(
format!(
"Tried to deserialize this server response as a MethodResponse json:\n{}",
&res_text
),
crate::DEBUG,
);
return Err(err);
}
Ok(s) => s,
};
Ok(res_text)
}
} }

View File

@ -26,3 +26,6 @@ pub use email::*;
mod mailbox; mod mailbox;
pub use mailbox::*; pub use mailbox::*;
mod thread;
pub use thread::*;

View File

@ -33,13 +33,6 @@ use std::hash::Hasher;
mod import; mod import;
pub use import::*; pub use import::*;
#[derive(Debug)]
pub struct ThreadObject;
impl Object for ThreadObject {
const NAME: &'static str = "Thread";
}
impl Id<EmailObject> { impl Id<EmailObject> {
pub fn into_hash(&self) -> EnvelopeHash { pub fn into_hash(&self) -> EnvelopeHash {
let mut h = DefaultHasher::new(); let mut h = DefaultHasher::new();

View File

@ -0,0 +1,80 @@
/*
* meli - jmap module.
*
* Copyright 2019-2022 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
use core::marker::PhantomData;
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ThreadObject {
#[serde(default)]
pub id: Id<ThreadObject>,
#[serde(default)]
pub email_ids: Vec<Id<EmailObject>>,
}
impl Object for ThreadObject {
const NAME: &'static str = "Thread";
}
impl ThreadObject {
_impl!(get email_ids, email_ids: Vec<Id<EmailObject>>);
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ThreadGet {
#[serde(flatten)]
pub get_call: Get<ThreadObject>,
}
impl Method<ThreadObject> for ThreadGet {
const NAME: &'static str = "Thread/get";
}
impl ThreadGet {
pub const RESULT_FIELD_THREAD_IDS: ResultField<EmailGet, EmailObject> =
ResultField::<EmailGet, EmailObject> {
field: "/list/*/threadId",
_ph: PhantomData,
};
pub fn new(get_call: Get<ThreadObject>) -> Self {
ThreadGet { get_call }
}
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ThreadChanges {
#[serde(flatten)]
pub changes_call: Changes<ThreadObject>,
}
impl Method<ThreadObject> for ThreadChanges {
const NAME: &'static str = "Thread/changes";
}
impl ThreadChanges {
pub fn new(changes_call: Changes<ThreadObject>) -> Self {
ThreadChanges { changes_call }
}
}

View File

@ -27,7 +27,7 @@ use std::convert::{TryFrom, TryInto};
pub type UtcDate = String; pub type UtcDate = String;
use super::rfc8620::Object; use super::rfc8620::{Object, State};
macro_rules! get_request_no { macro_rules! get_request_no {
($lock:expr) => {{ ($lock:expr) => {{
@ -86,30 +86,16 @@ pub struct JsonResponse<'a> {
pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash, JmapMailbox>> { pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash, JmapMailbox>> {
let seq = get_request_no!(conn.request_no); let seq = get_request_no!(conn.request_no);
let api_url = conn.session.lock().unwrap().api_url.clone(); let res_text = conn
let mut res = conn .send_request(serde_json::to_string(&json!({
.client "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
.post_async( "methodCalls": [["Mailbox/get", {
api_url.as_str(), "accountId": conn.mail_account_id()
serde_json::to_string(&json!({ },
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], format!("#m{}",seq).as_str()]],
"methodCalls": [["Mailbox/get", { }))?)
"accountId": conn.mail_account_id()
},
format!("#m{}",seq).as_str()]],
}))?,
)
.await?; .await?;
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
let res_text = res.text().await?;
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
Err(err) => {
let err = MeliError::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
return Err(err);
}
Ok(s) => s,
};
*conn.store.online_status.lock().await = (std::time::Instant::now(), Ok(())); *conn.store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
let m = GetResponse::<MailboxObject>::try_from(v.method_responses.remove(0))?; let m = GetResponse::<MailboxObject>::try_from(v.method_responses.remove(0))?;
let GetResponse::<MailboxObject> { let GetResponse::<MailboxObject> {
@ -249,109 +235,131 @@ pub async fn get_message(conn: &JmapConnection, ids: &[String]) -> Result<Vec<En
} }
*/ */
pub async fn fetch( #[derive(Copy, Clone)]
conn: &JmapConnection, pub enum EmailFetchState {
store: &Store, Start { batch_size: u64 },
mailbox_hash: MailboxHash, Ongoing { position: u64, batch_size: u64 },
) -> Result<Vec<Envelope>> { }
let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone();
let email_query_call: EmailQuery = EmailQuery::new(
Query::new()
.account_id(conn.mail_account_id().clone())
.filter(Some(Filter::Condition(
EmailFilterCondition::new()
.in_mailbox(Some(mailbox_id))
.into(),
)))
.position(0),
)
.collapse_threads(false);
let mut req = Request::new(conn.request_no.clone()); impl EmailFetchState {
let prev_seq = req.add_call(&email_query_call); pub async fn must_update_state(
&mut self,
let email_call: EmailGet = EmailGet::new( conn: &JmapConnection,
Get::new() mailbox_hash: MailboxHash,
.ids(Some(JmapArgument::reference( state: State<EmailObject>,
prev_seq, ) -> Result<bool> {
EmailQuery::RESULT_FIELD_IDS, {
))) let (is_empty, is_equal) = {
.account_id(conn.mail_account_id().clone()), let mailboxes_lck = conn.store.mailboxes.read().unwrap();
); mailboxes_lck
.get(&mailbox_hash)
req.add_call(&email_call); .map(|mbox| {
let current_state_lck = mbox.email_state.lock().unwrap();
let api_url = conn.session.lock().unwrap().api_url.clone(); (
let mut res = conn current_state_lck.is_none(),
.client current_state_lck.as_ref() != Some(&state),
.post_async(api_url.as_str(), serde_json::to_string(&req)?) )
.await?; })
.unwrap_or((true, true))
let res_text = res.text().await?; };
if is_empty {
let mut v: MethodResponse = match serde_json::from_str(&res_text) { let mut mailboxes_lck = conn.store.mailboxes.write().unwrap();
Err(err) => { debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
let err = MeliError::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); mailboxes_lck.entry(mailbox_hash).and_modify(|mbox| {
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); *mbox.email_state.lock().unwrap() = Some(state);
return Err(err); });
} else if !is_equal {
conn.email_changes(mailbox_hash).await?;
}
Ok(is_empty || !is_equal)
} }
Ok(s) => s, }
};
let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?; pub async fn fetch(
let query_response = QueryResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?; &mut self,
store conn: &JmapConnection,
.mailboxes store: &Store,
.write() mailbox_hash: MailboxHash,
.unwrap() ) -> Result<Vec<Envelope>> {
.entry(mailbox_hash) loop {
.and_modify(|mbox| { match *self {
*mbox.email_query_state.lock().unwrap() = Some(query_response.query_state); EmailFetchState::Start { batch_size } => {
}); *self = EmailFetchState::Ongoing {
let GetResponse::<EmailObject> { list, state, .. } = e; position: 0,
{ batch_size,
let (is_empty, is_equal) = { };
let mailboxes_lck = conn.store.mailboxes.read().unwrap(); continue;
mailboxes_lck }
.get(&mailbox_hash) EmailFetchState::Ongoing {
.map(|mbox| { mut position,
let current_state_lck = mbox.email_state.lock().unwrap(); batch_size,
( } => {
current_state_lck.is_none(), let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone();
current_state_lck.as_ref() != Some(&state), let email_query_call: EmailQuery = EmailQuery::new(
Query::new()
.account_id(conn.mail_account_id().clone())
.filter(Some(Filter::Condition(
EmailFilterCondition::new()
.in_mailbox(Some(mailbox_id))
.into(),
)))
.position(position)
.limit(Some(batch_size)),
) )
}) .collapse_threads(false);
.unwrap_or((true, true))
}; let mut req = Request::new(conn.request_no.clone());
if is_empty { let prev_seq = req.add_call(&email_query_call);
let mut mailboxes_lck = conn.store.mailboxes.write().unwrap();
debug!("{:?}: inserting state {}", EmailObject::NAME, &state); let email_call: EmailGet = EmailGet::new(
mailboxes_lck.entry(mailbox_hash).and_modify(|mbox| { Get::new()
*mbox.email_state.lock().unwrap() = Some(state); .ids(Some(JmapArgument::reference(
}); prev_seq,
} else if !is_equal { EmailQuery::RESULT_FIELD_IDS,
conn.email_changes(mailbox_hash).await?; )))
.account_id(conn.mail_account_id().clone()),
);
let _prev_seq = req.add_call(&email_call);
let res_text = conn.send_request(serde_json::to_string(&req)?).await?;
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
let e =
GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
let GetResponse::<EmailObject> { list, state, .. } = e;
if self.must_update_state(conn, mailbox_hash, state).await? {
*self = EmailFetchState::Start { batch_size };
continue;
}
let mut total = BTreeSet::default();
let mut unread = BTreeSet::default();
let mut ret = Vec::with_capacity(list.len());
for obj in list {
let env = store.add_envelope(obj);
total.insert(env.hash());
if !env.is_seen() {
unread.insert(env.hash());
}
ret.push(env);
}
let mut mailboxes_lck = store.mailboxes.write().unwrap();
mailboxes_lck.entry(mailbox_hash).and_modify(|mbox| {
mbox.total_emails.lock().unwrap().insert_existing_set(total);
mbox.unread_emails
.lock()
.unwrap()
.insert_existing_set(unread);
});
position += batch_size;
*self = EmailFetchState::Ongoing {
position,
batch_size,
};
return Ok(ret);
}
}
} }
} }
let mut total = BTreeSet::default();
let mut unread = BTreeSet::default();
let mut ret = Vec::with_capacity(list.len());
for obj in list {
let env = store.add_envelope(obj);
total.insert(env.hash());
if !env.is_seen() {
unread.insert(env.hash());
}
ret.push(env);
}
let mut mailboxes_lck = store.mailboxes.write().unwrap();
mailboxes_lck.entry(mailbox_hash).and_modify(|mbox| {
mbox.total_emails.lock().unwrap().insert_existing_set(total);
mbox.unread_emails
.lock()
.unwrap()
.insert_existing_set(unread);
});
Ok(ret)
} }
pub fn keywords_to_flags(keywords: Vec<String>) -> (Flag, Vec<String>) { pub fn keywords_to_flags(keywords: Vec<String>) -> (Flag, Vec<String>) {

View File

@ -202,7 +202,7 @@ impl<OBJ> State<OBJ> {
} }
} }
#[derive(Deserialize, Serialize, Debug, Default)] #[derive(Deserialize, Serialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct JmapSession { pub struct JmapSession {
pub capabilities: HashMap<String, CapabilitiesObject>, pub capabilities: HashMap<String, CapabilitiesObject>,
@ -223,7 +223,7 @@ impl Object for JmapSession {
const NAME: &'static str = "Session"; const NAME: &'static str = "Session";
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Clone, Default, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CapabilitiesObject { pub struct CapabilitiesObject {
#[serde(default)] #[serde(default)]
@ -244,7 +244,7 @@ pub struct CapabilitiesObject {
pub collation_algorithms: Vec<String>, pub collation_algorithms: Vec<String>,
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Account { pub struct Account {
pub name: String, pub name: String,
@ -259,7 +259,7 @@ impl Object for Account {
const NAME: &'static str = "Account"; const NAME: &'static str = "Account";
} }
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
pub struct BlobObject; pub struct BlobObject;
impl Object for BlobObject { impl Object for BlobObject {
@ -276,7 +276,7 @@ impl Object for BlobObject {
/// ///
/// The id of the account to use. /// The id of the account to use.
/// ///
#[derive(Deserialize, Debug)] #[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Get<OBJ: Object> pub struct Get<OBJ: Object>
where where
@ -389,7 +389,7 @@ impl<OBJ: Object + Serialize + std::fmt::Debug> Serialize for Get<OBJ> {
} }
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MethodResponse<'a> { pub struct MethodResponse<'a> {
#[serde(borrow)] #[serde(borrow)]
@ -400,7 +400,7 @@ pub struct MethodResponse<'a> {
pub session_state: State<JmapSession>, pub session_state: State<JmapSession>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GetResponse<OBJ: Object> { pub struct GetResponse<OBJ: Object> {
pub account_id: Id<Account>, pub account_id: Id<Account>,
@ -427,7 +427,7 @@ impl<OBJ: Object> GetResponse<OBJ> {
_impl!(get_mut not_found_mut, not_found: Vec<Id<OBJ>>); _impl!(get_mut not_found_mut, not_found: Vec<Id<OBJ>>);
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Clone, Copy, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
enum JmapError { enum JmapError {
RequestTooLarge, RequestTooLarge,
@ -435,7 +435,7 @@ enum JmapError {
InvalidResultReference, InvalidResultReference,
} }
#[derive(Serialize, Debug)] #[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Query<F: FilterTrait<OBJ>, OBJ: Object> pub struct Query<F: FilterTrait<OBJ>, OBJ: Object>
where where
@ -499,7 +499,7 @@ pub fn bool_true() -> bool {
true true
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct QueryResponse<OBJ: Object> { pub struct QueryResponse<OBJ: Object> {
pub account_id: Id<Account>, pub account_id: Id<Account>,
@ -580,7 +580,7 @@ impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> {
/// positive integer greater than 0. If a value outside of this range /// positive integer greater than 0. If a value outside of this range
/// is given, the server MUST re /// is given, the server MUST re
/// ///
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
/* ch-ch-ch-ch-ch-Changes */ /* ch-ch-ch-ch-ch-Changes */
pub struct Changes<OBJ: Object> pub struct Changes<OBJ: Object>
@ -636,7 +636,7 @@ where
); );
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ChangesResponse<OBJ: Object> { pub struct ChangesResponse<OBJ: Object> {
pub account_id: Id<Account>, pub account_id: Id<Account>,
@ -678,7 +678,7 @@ impl<OBJ: Object> ChangesResponse<OBJ> {
///and dependencies that may exist if doing multiple operations at once ///and dependencies that may exist if doing multiple operations at once
///(for example, to ensure there is always a minimum number of a certain ///(for example, to ensure there is always a minimum number of a certain
///record type). ///record type).
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Set<OBJ: Object> pub struct Set<OBJ: Object>
where where
@ -788,7 +788,7 @@ where
_impl!(update: Option<HashMap<Id<OBJ>, Value>>); _impl!(update: Option<HashMap<Id<OBJ>, Value>>);
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct SetResponse<OBJ: Object> { pub struct SetResponse<OBJ: Object> {
///o accountId: "Id" ///o accountId: "Id"
@ -859,7 +859,7 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for SetRes
} }
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[serde(tag = "type", content = "description")] #[serde(tag = "type", content = "description")]
pub enum SetError { pub enum SetError {
@ -992,7 +992,7 @@ pub fn upload_request_format(upload_url: &str, account_id: &Id<Account>) -> Stri
ret ret
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UploadResponse { pub struct UploadResponse {
///o accountId: "Id" ///o accountId: "Id"
@ -1024,7 +1024,7 @@ pub struct UploadResponse {
/// The "Foo/queryChanges" method allows a client to efficiently update /// The "Foo/queryChanges" method allows a client to efficiently update
/// the state of a cached query to match the new state on the server. It /// the state of a cached query to match the new state on the server. It
/// takes the following arguments: /// takes the following arguments:
#[derive(Serialize, Debug)] #[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct QueryChanges<F: FilterTrait<OBJ>, OBJ: Object> pub struct QueryChanges<F: FilterTrait<OBJ>, OBJ: Object>
where where
@ -1093,7 +1093,7 @@ where
_impl!(calculate_total: bool); _impl!(calculate_total: bool);
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct QueryChangesResponse<OBJ: Object> { pub struct QueryChangesResponse<OBJ: Object> {
/// The id of the account used for the call. /// The id of the account used for the call.
@ -1170,7 +1170,7 @@ pub struct QueryChangesResponse<OBJ: Object> {
pub added: Vec<AddedItem<OBJ>>, pub added: Vec<AddedItem<OBJ>>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AddedItem<OBJ: Object> { pub struct AddedItem<OBJ: Object> {
pub id: Id<OBJ>, pub id: Id<OBJ>,

View File

@ -23,9 +23,9 @@ use crate::backends::jmap::protocol::Method;
use crate::backends::jmap::rfc8620::Object; use crate::backends::jmap::rfc8620::Object;
use crate::backends::jmap::rfc8620::ResultField; use crate::backends::jmap::rfc8620::ResultField;
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum JmapArgument<T> { pub enum JmapArgument<T: Clone> {
Value(T), Value(T),
ResultReference { ResultReference {
result_of: String, result_of: String,
@ -34,7 +34,7 @@ pub enum JmapArgument<T> {
}, },
} }
impl<T> JmapArgument<T> { impl<T: Clone> JmapArgument<T> {
pub fn value(v: T) -> Self { pub fn value(v: T) -> Self {
JmapArgument::Value(v) JmapArgument::Value(v)
} }

View File

@ -21,7 +21,7 @@
use super::*; use super::*;
#[derive(Serialize, Debug)] #[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Comparator<OBJ: Object> { pub struct Comparator<OBJ: Object> {
property: String, property: String,