Compare commits
1 Commits
master
...
jmap-batch
Author | SHA1 | Date |
---|---|---|
Manos Pitsidianakis | e64f2077a8 |
|
@ -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;
|
||||
use operations::*;
|
||||
|
||||
|
@ -196,6 +199,7 @@ pub struct Store {
|
|||
pub mailbox_state: Arc<Mutex<State<MailboxObject>>>,
|
||||
pub online_status: Arc<FutureMutex<(Instant, Result<()>)>>,
|
||||
pub is_subscribed: Arc<IsSubscribedFn>,
|
||||
pub core_capabilities: Arc<Mutex<rfc8620::CapabilitiesObject>>,
|
||||
pub event_consumer: BackendEventConsumer,
|
||||
}
|
||||
|
||||
|
@ -330,15 +334,19 @@ impl MailBackend for JmapType {
|
|||
Ok(Box::pin(async_stream::try_stream! {
|
||||
let mut conn = connection.lock().await;
|
||||
conn.connect().await?;
|
||||
let res = protocol::fetch(
|
||||
&conn,
|
||||
&store,
|
||||
mailbox_hash,
|
||||
).await?;
|
||||
if res.is_empty() {
|
||||
return;
|
||||
let batch_size: u64 = conn.store.core_capabilities.lock().unwrap().max_objects_in_get;
|
||||
let mut fetch_state = protocol::EmailFetchState::Start { batch_size };
|
||||
loop {
|
||||
let res = fetch_state.fetch(
|
||||
&conn,
|
||||
&store,
|
||||
mailbox_hash,
|
||||
).await?;
|
||||
if res.is_empty() {
|
||||
return;
|
||||
}
|
||||
yield res;
|
||||
}
|
||||
yield res;
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -889,7 +897,7 @@ impl JmapType {
|
|||
event_consumer,
|
||||
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
|
||||
collection: Collection::default(),
|
||||
|
||||
core_capabilities: Arc::new(Mutex::new(rfc8620::CapabilitiesObject::default())),
|
||||
byte_cache: Default::default(),
|
||||
id_store: Default::default(),
|
||||
reverse_id_store: Default::default(),
|
||||
|
|
|
@ -88,19 +88,15 @@ impl JmapConnection {
|
|||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
if !session
|
||||
.capabilities
|
||||
.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(", ")));
|
||||
if !session.capabilities.contains_key(JMAP_CORE_CAPABILITY) {
|
||||
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));
|
||||
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
||||
return Err(err);
|
||||
}
|
||||
if !session
|
||||
.capabilities
|
||||
.contains_key("urn:ietf:params:jmap:mail")
|
||||
{
|
||||
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.core_capabilities.lock().unwrap() =
|
||||
session.capabilities["urn:ietf:params:jmap:core"].clone();
|
||||
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));
|
||||
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
||||
return Err(err);
|
||||
}
|
||||
|
@ -111,7 +107,7 @@ impl JmapConnection {
|
|||
}
|
||||
|
||||
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> {
|
||||
|
@ -359,4 +355,29 @@ impl JmapConnection {
|
|||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,3 +26,6 @@ pub use email::*;
|
|||
|
||||
mod mailbox;
|
||||
pub use mailbox::*;
|
||||
|
||||
mod thread;
|
||||
pub use thread::*;
|
||||
|
|
|
@ -33,13 +33,6 @@ use std::hash::Hasher;
|
|||
mod import;
|
||||
pub use import::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadObject;
|
||||
|
||||
impl Object for ThreadObject {
|
||||
const NAME: &'static str = "Thread";
|
||||
}
|
||||
|
||||
impl Id<EmailObject> {
|
||||
pub fn into_hash(&self) -> EnvelopeHash {
|
||||
let mut h = DefaultHasher::new();
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ use std::convert::{TryFrom, TryInto};
|
|||
|
||||
pub type UtcDate = String;
|
||||
|
||||
use super::rfc8620::Object;
|
||||
use super::rfc8620::{Object, State};
|
||||
|
||||
macro_rules! get_request_no {
|
||||
($lock:expr) => {{
|
||||
|
@ -86,30 +86,16 @@ pub struct JsonResponse<'a> {
|
|||
|
||||
pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash, JmapMailbox>> {
|
||||
let seq = get_request_no!(conn.request_no);
|
||||
let api_url = conn.session.lock().unwrap().api_url.clone();
|
||||
let mut res = conn
|
||||
.client
|
||||
.post_async(
|
||||
api_url.as_str(),
|
||||
serde_json::to_string(&json!({
|
||||
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
|
||||
"methodCalls": [["Mailbox/get", {
|
||||
"accountId": conn.mail_account_id()
|
||||
},
|
||||
format!("#m{}",seq).as_str()]],
|
||||
}))?,
|
||||
)
|
||||
let res_text = conn
|
||||
.send_request(serde_json::to_string(&json!({
|
||||
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
|
||||
"methodCalls": [["Mailbox/get", {
|
||||
"accountId": conn.mail_account_id()
|
||||
},
|
||||
format!("#m{}",seq).as_str()]],
|
||||
}))?)
|
||||
.await?;
|
||||
|
||||
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,
|
||||
};
|
||||
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
|
||||
*conn.store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
|
||||
let m = GetResponse::<MailboxObject>::try_from(v.method_responses.remove(0))?;
|
||||
let GetResponse::<MailboxObject> {
|
||||
|
@ -249,109 +235,131 @@ pub async fn get_message(conn: &JmapConnection, ids: &[String]) -> Result<Vec<En
|
|||
}
|
||||
*/
|
||||
|
||||
pub async fn fetch(
|
||||
conn: &JmapConnection,
|
||||
store: &Store,
|
||||
mailbox_hash: MailboxHash,
|
||||
) -> 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);
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum EmailFetchState {
|
||||
Start { batch_size: u64 },
|
||||
Ongoing { position: u64, batch_size: u64 },
|
||||
}
|
||||
|
||||
let mut req = Request::new(conn.request_no.clone());
|
||||
let prev_seq = req.add_call(&email_query_call);
|
||||
|
||||
let email_call: EmailGet = EmailGet::new(
|
||||
Get::new()
|
||||
.ids(Some(JmapArgument::reference(
|
||||
prev_seq,
|
||||
EmailQuery::RESULT_FIELD_IDS,
|
||||
)))
|
||||
.account_id(conn.mail_account_id().clone()),
|
||||
);
|
||||
|
||||
req.add_call(&email_call);
|
||||
|
||||
let api_url = conn.session.lock().unwrap().api_url.clone();
|
||||
let mut res = conn
|
||||
.client
|
||||
.post_async(api_url.as_str(), serde_json::to_string(&req)?)
|
||||
.await?;
|
||||
|
||||
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);
|
||||
impl EmailFetchState {
|
||||
pub async fn must_update_state(
|
||||
&mut self,
|
||||
conn: &JmapConnection,
|
||||
mailbox_hash: MailboxHash,
|
||||
state: State<EmailObject>,
|
||||
) -> Result<bool> {
|
||||
{
|
||||
let (is_empty, is_equal) = {
|
||||
let mailboxes_lck = conn.store.mailboxes.read().unwrap();
|
||||
mailboxes_lck
|
||||
.get(&mailbox_hash)
|
||||
.map(|mbox| {
|
||||
let current_state_lck = mbox.email_state.lock().unwrap();
|
||||
(
|
||||
current_state_lck.is_none(),
|
||||
current_state_lck.as_ref() != Some(&state),
|
||||
)
|
||||
})
|
||||
.unwrap_or((true, true))
|
||||
};
|
||||
if is_empty {
|
||||
let mut mailboxes_lck = conn.store.mailboxes.write().unwrap();
|
||||
debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
|
||||
mailboxes_lck.entry(mailbox_hash).and_modify(|mbox| {
|
||||
*mbox.email_state.lock().unwrap() = Some(state);
|
||||
});
|
||||
} 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())?;
|
||||
let query_response = QueryResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
||||
store
|
||||
.mailboxes
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(mailbox_hash)
|
||||
.and_modify(|mbox| {
|
||||
*mbox.email_query_state.lock().unwrap() = Some(query_response.query_state);
|
||||
});
|
||||
let GetResponse::<EmailObject> { list, state, .. } = e;
|
||||
{
|
||||
let (is_empty, is_equal) = {
|
||||
let mailboxes_lck = conn.store.mailboxes.read().unwrap();
|
||||
mailboxes_lck
|
||||
.get(&mailbox_hash)
|
||||
.map(|mbox| {
|
||||
let current_state_lck = mbox.email_state.lock().unwrap();
|
||||
(
|
||||
current_state_lck.is_none(),
|
||||
current_state_lck.as_ref() != Some(&state),
|
||||
}
|
||||
|
||||
pub async fn fetch(
|
||||
&mut self,
|
||||
conn: &JmapConnection,
|
||||
store: &Store,
|
||||
mailbox_hash: MailboxHash,
|
||||
) -> Result<Vec<Envelope>> {
|
||||
loop {
|
||||
match *self {
|
||||
EmailFetchState::Start { batch_size } => {
|
||||
*self = EmailFetchState::Ongoing {
|
||||
position: 0,
|
||||
batch_size,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
EmailFetchState::Ongoing {
|
||||
mut position,
|
||||
batch_size,
|
||||
} => {
|
||||
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(position)
|
||||
.limit(Some(batch_size)),
|
||||
)
|
||||
})
|
||||
.unwrap_or((true, true))
|
||||
};
|
||||
if is_empty {
|
||||
let mut mailboxes_lck = conn.store.mailboxes.write().unwrap();
|
||||
debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
|
||||
mailboxes_lck.entry(mailbox_hash).and_modify(|mbox| {
|
||||
*mbox.email_state.lock().unwrap() = Some(state);
|
||||
});
|
||||
} else if !is_equal {
|
||||
conn.email_changes(mailbox_hash).await?;
|
||||
.collapse_threads(false);
|
||||
|
||||
let mut req = Request::new(conn.request_no.clone());
|
||||
let prev_seq = req.add_call(&email_query_call);
|
||||
|
||||
let email_call: EmailGet = EmailGet::new(
|
||||
Get::new()
|
||||
.ids(Some(JmapArgument::reference(
|
||||
prev_seq,
|
||||
EmailQuery::RESULT_FIELD_IDS,
|
||||
)))
|
||||
.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>) {
|
||||
|
|
|
@ -202,7 +202,7 @@ impl<OBJ> State<OBJ> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JmapSession {
|
||||
pub capabilities: HashMap<String, CapabilitiesObject>,
|
||||
|
@ -223,7 +223,7 @@ impl Object for JmapSession {
|
|||
const NAME: &'static str = "Session";
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CapabilitiesObject {
|
||||
#[serde(default)]
|
||||
|
@ -244,7 +244,7 @@ pub struct CapabilitiesObject {
|
|||
pub collation_algorithms: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Account {
|
||||
pub name: String,
|
||||
|
@ -259,7 +259,7 @@ impl Object for Account {
|
|||
const NAME: &'static str = "Account";
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct BlobObject;
|
||||
|
||||
impl Object for BlobObject {
|
||||
|
@ -276,7 +276,7 @@ impl Object for BlobObject {
|
|||
///
|
||||
/// The id of the account to use.
|
||||
///
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Get<OBJ: Object>
|
||||
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")]
|
||||
pub struct MethodResponse<'a> {
|
||||
#[serde(borrow)]
|
||||
|
@ -400,7 +400,7 @@ pub struct MethodResponse<'a> {
|
|||
pub session_state: State<JmapSession>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetResponse<OBJ: Object> {
|
||||
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>>);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Clone, Copy, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum JmapError {
|
||||
RequestTooLarge,
|
||||
|
@ -435,7 +435,7 @@ enum JmapError {
|
|||
InvalidResultReference,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Query<F: FilterTrait<OBJ>, OBJ: Object>
|
||||
where
|
||||
|
@ -499,7 +499,7 @@ pub fn bool_true() -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueryResponse<OBJ: Object> {
|
||||
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
|
||||
/// is given, the server MUST re
|
||||
///
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/* ch-ch-ch-ch-ch-Changes */
|
||||
pub struct Changes<OBJ: Object>
|
||||
|
@ -636,7 +636,7 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ChangesResponse<OBJ: Object> {
|
||||
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
|
||||
///(for example, to ensure there is always a minimum number of a certain
|
||||
///record type).
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Set<OBJ: Object>
|
||||
where
|
||||
|
@ -788,7 +788,7 @@ where
|
|||
_impl!(update: Option<HashMap<Id<OBJ>, Value>>);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetResponse<OBJ: Object> {
|
||||
///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(tag = "type", content = "description")]
|
||||
pub enum SetError {
|
||||
|
@ -992,7 +992,7 @@ pub fn upload_request_format(upload_url: &str, account_id: &Id<Account>) -> Stri
|
|||
ret
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UploadResponse {
|
||||
///o accountId: "Id"
|
||||
|
@ -1024,7 +1024,7 @@ pub struct UploadResponse {
|
|||
/// 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
|
||||
/// takes the following arguments:
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueryChanges<F: FilterTrait<OBJ>, OBJ: Object>
|
||||
where
|
||||
|
@ -1093,7 +1093,7 @@ where
|
|||
_impl!(calculate_total: bool);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueryChangesResponse<OBJ: Object> {
|
||||
/// The id of the account used for the call.
|
||||
|
@ -1170,7 +1170,7 @@ pub struct QueryChangesResponse<OBJ: Object> {
|
|||
pub added: Vec<AddedItem<OBJ>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AddedItem<OBJ: Object> {
|
||||
pub id: Id<OBJ>,
|
||||
|
|
|
@ -23,9 +23,9 @@ use crate::backends::jmap::protocol::Method;
|
|||
use crate::backends::jmap::rfc8620::Object;
|
||||
use crate::backends::jmap::rfc8620::ResultField;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum JmapArgument<T> {
|
||||
pub enum JmapArgument<T: Clone> {
|
||||
Value(T),
|
||||
ResultReference {
|
||||
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 {
|
||||
JmapArgument::Value(v)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
use super::*;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Comparator<OBJ: Object> {
|
||||
property: String,
|
||||
|
|
Loading…
Reference in New Issue