melib/jmap: add Type parameter to Id, State
Make Id, State have a type parameter to the object it refers to (eg `Id<EmailObject>`) instead of just a Stringjmap-eventsource
parent
19d4a191d8
commit
425f4b9930
|
@ -191,15 +191,16 @@ macro_rules! get_conf_val {
|
||||||
pub struct Store {
|
pub struct Store {
|
||||||
pub account_name: Arc<String>,
|
pub account_name: Arc<String>,
|
||||||
pub account_hash: AccountHash,
|
pub account_hash: AccountHash,
|
||||||
pub account_id: Arc<Mutex<String>>,
|
pub account_id: Arc<Mutex<Id<Account>>>,
|
||||||
pub byte_cache: Arc<Mutex<HashMap<EnvelopeHash, EnvelopeCache>>>,
|
pub byte_cache: Arc<Mutex<HashMap<EnvelopeHash, EnvelopeCache>>>,
|
||||||
pub id_store: Arc<Mutex<HashMap<EnvelopeHash, Id>>>,
|
pub id_store: Arc<Mutex<HashMap<EnvelopeHash, Id<EmailObject>>>>,
|
||||||
pub reverse_id_store: Arc<Mutex<HashMap<Id, EnvelopeHash>>>,
|
pub reverse_id_store: Arc<Mutex<HashMap<Id<EmailObject>, EnvelopeHash>>>,
|
||||||
pub blob_id_store: Arc<Mutex<HashMap<EnvelopeHash, Id>>>,
|
pub blob_id_store: Arc<Mutex<HashMap<EnvelopeHash, Id<BlobObject>>>>,
|
||||||
pub tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
|
pub tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
|
||||||
pub mailboxes: Arc<RwLock<HashMap<MailboxHash, JmapMailbox>>>,
|
pub mailboxes: Arc<RwLock<HashMap<MailboxHash, JmapMailbox>>>,
|
||||||
pub mailboxes_index: Arc<RwLock<HashMap<MailboxHash, HashSet<EnvelopeHash>>>>,
|
pub mailboxes_index: Arc<RwLock<HashMap<MailboxHash, HashSet<EnvelopeHash>>>>,
|
||||||
pub object_set_states: Arc<Mutex<HashMap<&'static str, String>>>,
|
pub email_state: Arc<Mutex<State<EmailObject>>>,
|
||||||
|
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 event_consumer: BackendEventConsumer,
|
pub event_consumer: BackendEventConsumer,
|
||||||
|
@ -275,7 +276,7 @@ impl Store {
|
||||||
|
|
||||||
pub fn remove_envelope(
|
pub fn remove_envelope(
|
||||||
&self,
|
&self,
|
||||||
obj_id: Id,
|
obj_id: Id<EmailObject>,
|
||||||
) -> Option<(EnvelopeHash, SmallVec<[MailboxHash; 8]>)> {
|
) -> Option<(EnvelopeHash, SmallVec<[MailboxHash; 8]>)> {
|
||||||
let env_hash = self.reverse_id_store.lock().unwrap().remove(&obj_id)?;
|
let env_hash = self.reverse_id_store.lock().unwrap().remove(&obj_id)?;
|
||||||
self.id_store.lock().unwrap().remove(&env_hash);
|
self.id_store.lock().unwrap().remove(&env_hash);
|
||||||
|
@ -413,7 +414,7 @@ impl MailBackend for JmapType {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mailbox_id: String = {
|
let mailbox_id: Id<MailboxObject> = {
|
||||||
let mailboxes_lck = store.mailboxes.read().unwrap();
|
let mailboxes_lck = store.mailboxes.read().unwrap();
|
||||||
if let Some(mailbox) = mailboxes_lck.get(&mailbox_hash) {
|
if let Some(mailbox) = mailboxes_lck.get(&mailbox_hash) {
|
||||||
mailbox.id.clone()
|
mailbox.id.clone()
|
||||||
|
@ -428,7 +429,7 @@ impl MailBackend for JmapType {
|
||||||
|
|
||||||
let upload_response: UploadResponse = serde_json::from_str(&res_text)?;
|
let upload_response: UploadResponse = serde_json::from_str(&res_text)?;
|
||||||
let mut req = Request::new(conn.request_no.clone());
|
let mut req = Request::new(conn.request_no.clone());
|
||||||
let creation_id = "1".to_string();
|
let creation_id: Id<EmailObject> = "1".to_string().into();
|
||||||
let mut email_imports = HashMap::default();
|
let mut email_imports = HashMap::default();
|
||||||
let mut mailbox_ids = HashMap::default();
|
let mut mailbox_ids = HashMap::default();
|
||||||
mailbox_ids.insert(mailbox_id, true);
|
mailbox_ids.insert(mailbox_id, true);
|
||||||
|
@ -440,7 +441,7 @@ impl MailBackend for JmapType {
|
||||||
);
|
);
|
||||||
|
|
||||||
let import_call: ImportCall = ImportCall::new()
|
let import_call: ImportCall = ImportCall::new()
|
||||||
.account_id(conn.mail_account_id().to_string())
|
.account_id(conn.mail_account_id().clone())
|
||||||
.emails(email_imports);
|
.emails(email_imports);
|
||||||
|
|
||||||
req.add_call(&import_call);
|
req.add_call(&import_call);
|
||||||
|
@ -508,7 +509,7 @@ impl MailBackend for JmapType {
|
||||||
conn.connect().await?;
|
conn.connect().await?;
|
||||||
let email_call: EmailQuery = EmailQuery::new(
|
let email_call: EmailQuery = EmailQuery::new(
|
||||||
Query::new()
|
Query::new()
|
||||||
.account_id(conn.mail_account_id().to_string())
|
.account_id(conn.mail_account_id().clone())
|
||||||
.filter(Some(filter))
|
.filter(Some(filter))
|
||||||
.position(0),
|
.position(0),
|
||||||
)
|
)
|
||||||
|
@ -527,14 +528,7 @@ impl MailBackend for JmapType {
|
||||||
*store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
|
*store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
|
||||||
let m = QueryResponse::<EmailObject>::try_from(v.method_responses.remove(0))?;
|
let m = QueryResponse::<EmailObject>::try_from(v.method_responses.remove(0))?;
|
||||||
let QueryResponse::<EmailObject> { ids, .. } = m;
|
let QueryResponse::<EmailObject> { ids, .. } = m;
|
||||||
let ret = ids
|
let ret = ids.into_iter().map(|id| id.into_hash()).collect();
|
||||||
.into_iter()
|
|
||||||
.map(|id| {
|
|
||||||
let mut h = DefaultHasher::new();
|
|
||||||
h.write(id.as_bytes());
|
|
||||||
h.finish()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -584,9 +578,9 @@ impl MailBackend for JmapType {
|
||||||
mailboxes_lck[&destination_mailbox_hash].id.clone(),
|
mailboxes_lck[&destination_mailbox_hash].id.clone(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let mut update_map: HashMap<String, Value> = HashMap::default();
|
let mut update_map: HashMap<Id<EmailObject>, Value> = HashMap::default();
|
||||||
let mut ids: Vec<Id> = Vec::with_capacity(env_hashes.rest.len() + 1);
|
let mut ids: Vec<Id<EmailObject>> = Vec::with_capacity(env_hashes.rest.len() + 1);
|
||||||
let mut id_map: HashMap<Id, EnvelopeHash> = HashMap::default();
|
let mut id_map: HashMap<Id<EmailObject>, EnvelopeHash> = HashMap::default();
|
||||||
let mut update_keywords: HashMap<String, Value> = HashMap::default();
|
let mut update_keywords: HashMap<String, Value> = HashMap::default();
|
||||||
update_keywords.insert(
|
update_keywords.insert(
|
||||||
format!("mailboxIds/{}", &destination_mailbox_id),
|
format!("mailboxIds/{}", &destination_mailbox_id),
|
||||||
|
@ -594,7 +588,7 @@ impl MailBackend for JmapType {
|
||||||
);
|
);
|
||||||
if move_ {
|
if move_ {
|
||||||
update_keywords.insert(
|
update_keywords.insert(
|
||||||
format!("mailboxIds/{}", &source_mailbox_hash),
|
format!("mailboxIds/{}", &source_mailbox_id),
|
||||||
serde_json::json!(null),
|
serde_json::json!(null),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -611,7 +605,7 @@ impl MailBackend for JmapType {
|
||||||
|
|
||||||
let email_set_call: EmailSet = EmailSet::new(
|
let email_set_call: EmailSet = EmailSet::new(
|
||||||
Set::<EmailObject>::new()
|
Set::<EmailObject>::new()
|
||||||
.account_id(conn.mail_account_id().to_string())
|
.account_id(conn.mail_account_id().clone())
|
||||||
.update(Some(update_map)),
|
.update(Some(update_map)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -653,9 +647,9 @@ impl MailBackend for JmapType {
|
||||||
let connection = self.connection.clone();
|
let connection = self.connection.clone();
|
||||||
Ok(Box::pin(async move {
|
Ok(Box::pin(async move {
|
||||||
let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone();
|
let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone();
|
||||||
let mut update_map: HashMap<String, Value> = HashMap::default();
|
let mut update_map: HashMap<Id<EmailObject>, Value> = HashMap::default();
|
||||||
let mut ids: Vec<Id> = Vec::with_capacity(env_hashes.rest.len() + 1);
|
let mut ids: Vec<Id<EmailObject>> = Vec::with_capacity(env_hashes.rest.len() + 1);
|
||||||
let mut id_map: HashMap<Id, EnvelopeHash> = HashMap::default();
|
let mut id_map: HashMap<Id<EmailObject>, EnvelopeHash> = HashMap::default();
|
||||||
let mut update_keywords: HashMap<String, Value> = HashMap::default();
|
let mut update_keywords: HashMap<String, Value> = HashMap::default();
|
||||||
for (flag, value) in flags.iter() {
|
for (flag, value) in flags.iter() {
|
||||||
match flag {
|
match flag {
|
||||||
|
@ -701,11 +695,11 @@ impl MailBackend for JmapType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let conn = connection.lock().await;
|
let mut conn = connection.lock().await;
|
||||||
|
|
||||||
let email_set_call: EmailSet = EmailSet::new(
|
let email_set_call: EmailSet = EmailSet::new(
|
||||||
Set::<EmailObject>::new()
|
Set::<EmailObject>::new()
|
||||||
.account_id(conn.mail_account_id().to_string())
|
.account_id(conn.mail_account_id().clone())
|
||||||
.update(Some(update_map)),
|
.update(Some(update_map)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -714,7 +708,7 @@ impl MailBackend for JmapType {
|
||||||
let email_call: EmailGet = EmailGet::new(
|
let email_call: EmailGet = EmailGet::new(
|
||||||
Get::new()
|
Get::new()
|
||||||
.ids(Some(JmapArgument::Value(ids)))
|
.ids(Some(JmapArgument::Value(ids)))
|
||||||
.account_id(conn.mail_account_id().to_string())
|
.account_id(conn.mail_account_id().clone())
|
||||||
.properties(Some(vec!["keywords".to_string()])),
|
.properties(Some(vec!["keywords".to_string()])),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -759,21 +753,15 @@ impl MailBackend for JmapType {
|
||||||
let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
||||||
let GetResponse::<EmailObject> { list, state, .. } = e;
|
let GetResponse::<EmailObject> { list, state, .. } = e;
|
||||||
{
|
{
|
||||||
let c = store
|
let (is_empty, is_equal) = {
|
||||||
.object_set_states
|
let current_state_lck = conn.store.email_state.lock().unwrap();
|
||||||
.lock()
|
(current_state_lck.is_empty(), *current_state_lck != state)
|
||||||
.unwrap()
|
};
|
||||||
.get(&EmailObject::NAME)
|
if is_empty {
|
||||||
.map(|prev_state| *prev_state == state);
|
|
||||||
if let Some(false) = c {
|
|
||||||
conn.email_changes().await?;
|
|
||||||
} else {
|
|
||||||
debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
|
debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
|
||||||
store
|
*conn.store.email_state.lock().unwrap() = state;
|
||||||
.object_set_states
|
} else if !is_equal {
|
||||||
.lock()
|
conn.email_changes().await?;
|
||||||
.unwrap()
|
|
||||||
.insert(EmailObject::NAME, state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!(&list);
|
debug!(&list);
|
||||||
|
@ -813,7 +801,7 @@ impl JmapType {
|
||||||
let store = Arc::new(Store {
|
let store = Arc::new(Store {
|
||||||
account_name: Arc::new(s.name.clone()),
|
account_name: Arc::new(s.name.clone()),
|
||||||
account_hash,
|
account_hash,
|
||||||
account_id: Arc::new(Mutex::new(String::new())),
|
account_id: Arc::new(Mutex::new(Id::new())),
|
||||||
online_status,
|
online_status,
|
||||||
event_consumer,
|
event_consumer,
|
||||||
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
|
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
|
||||||
|
@ -825,7 +813,8 @@ impl JmapType {
|
||||||
tag_index: Default::default(),
|
tag_index: Default::default(),
|
||||||
mailboxes: Default::default(),
|
mailboxes: Default::default(),
|
||||||
mailboxes_index: Default::default(),
|
mailboxes_index: Default::default(),
|
||||||
object_set_states: Default::default(),
|
email_state: Default::default(),
|
||||||
|
mailbox_state: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Box::new(JmapType {
|
Ok(Box::new(JmapType {
|
||||||
|
|
|
@ -100,7 +100,7 @@ impl JmapConnection {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mail_account_id(&self) -> &Id {
|
pub fn mail_account_id(&self) -> &Id<Account> {
|
||||||
&self.session.primary_accounts["urn:ietf:params:jmap:mail"]
|
&self.session.primary_accounts["urn:ietf:params:jmap:mail"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,22 +109,16 @@ impl JmapConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn email_changes(&self) -> Result<()> {
|
pub async fn email_changes(&self) -> Result<()> {
|
||||||
let mut current_state: String = {
|
let mut current_state: State<EmailObject> = self.store.email_state.lock().unwrap().clone();
|
||||||
let object_set_states_lck = self.store.object_set_states.lock().unwrap();
|
if current_state.is_empty() {
|
||||||
let v = if let Some(prev_state) = debug!(object_set_states_lck.get(&EmailObject::NAME))
|
debug!("{:?}: has no saved state", EmailObject::NAME);
|
||||||
{
|
return Ok(());
|
||||||
prev_state.clone()
|
}
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
drop(object_set_states_lck);
|
|
||||||
v
|
|
||||||
};
|
|
||||||
loop {
|
loop {
|
||||||
|
|
||||||
let email_changes_call: EmailChanges = EmailChanges::new(
|
let email_changes_call: EmailChanges = EmailChanges::new(
|
||||||
Changes::<EmailObject>::new()
|
Changes::<EmailObject>::new()
|
||||||
.account_id(self.mail_account_id().to_string())
|
.account_id(self.mail_account_id().clone())
|
||||||
.since_state(current_state.clone()),
|
.since_state(current_state.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -136,7 +130,7 @@ impl JmapConnection {
|
||||||
prev_seq,
|
prev_seq,
|
||||||
ResultField::<EmailChanges, EmailObject>::new("created"),
|
ResultField::<EmailChanges, EmailObject>::new("created"),
|
||||||
)))
|
)))
|
||||||
.account_id(self.mail_account_id().to_string()),
|
.account_id(self.mail_account_id().clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
req.add_call(&email_get_call);
|
req.add_call(&email_get_call);
|
||||||
|
@ -153,6 +147,12 @@ impl JmapConnection {
|
||||||
GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
||||||
debug!(&get_response);
|
debug!(&get_response);
|
||||||
let GetResponse::<EmailObject> { list, .. } = get_response;
|
let GetResponse::<EmailObject> { list, .. } = get_response;
|
||||||
|
let changes_response =
|
||||||
|
ChangesResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
||||||
|
if changes_response.new_state == current_state {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let mut mailbox_hashes: Vec<SmallVec<[MailboxHash; 8]>> =
|
let mut mailbox_hashes: Vec<SmallVec<[MailboxHash; 8]>> =
|
||||||
Vec::with_capacity(list.len());
|
Vec::with_capacity(list.len());
|
||||||
for envobj in &list {
|
for envobj in &list {
|
||||||
|
@ -189,9 +189,6 @@ impl JmapConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let changes_response =
|
|
||||||
ChangesResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
|
||||||
|
|
||||||
let ChangesResponse::<EmailObject> {
|
let ChangesResponse::<EmailObject> {
|
||||||
account_id: _,
|
account_id: _,
|
||||||
new_state,
|
new_state,
|
||||||
|
@ -218,11 +215,7 @@ impl JmapConnection {
|
||||||
if has_more_changes {
|
if has_more_changes {
|
||||||
current_state = new_state;
|
current_state = new_state;
|
||||||
} else {
|
} else {
|
||||||
self.store
|
*self.store.email_state.lock().unwrap() = new_state;
|
||||||
.object_set_states
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(EmailObject::NAME, new_state);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,10 +29,11 @@ pub struct JmapMailbox {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub hash: MailboxHash,
|
pub hash: MailboxHash,
|
||||||
pub children: Vec<MailboxHash>,
|
pub children: Vec<MailboxHash>,
|
||||||
pub id: String,
|
pub id: Id<MailboxObject>,
|
||||||
pub is_subscribed: bool,
|
pub is_subscribed: bool,
|
||||||
pub my_rights: JmapRights,
|
pub my_rights: JmapRights,
|
||||||
pub parent_id: Option<String>,
|
pub parent_id: Option<Id<MailboxObject>>,
|
||||||
|
pub parent_hash: Option<MailboxHash>,
|
||||||
pub role: Option<String>,
|
pub role: Option<String>,
|
||||||
pub sort_order: u64,
|
pub sort_order: u64,
|
||||||
pub total_emails: Arc<Mutex<u64>>,
|
pub total_emails: Arc<Mutex<u64>>,
|
||||||
|
@ -66,7 +67,7 @@ impl BackendMailbox for JmapMailbox {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parent(&self) -> Option<MailboxHash> {
|
fn parent(&self) -> Option<MailboxHash> {
|
||||||
None
|
self.parent_hash
|
||||||
}
|
}
|
||||||
|
|
||||||
fn permissions(&self) -> MailboxPermissions {
|
fn permissions(&self) -> MailboxPermissions {
|
||||||
|
|
|
@ -32,6 +32,21 @@ 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> {
|
||||||
|
pub fn into_hash(&self) -> EnvelopeHash {
|
||||||
|
let mut h = DefaultHasher::new();
|
||||||
|
h.write(self.inner.as_bytes());
|
||||||
|
h.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 4.1.1.
|
// 4.1.1.
|
||||||
// Metadata
|
// Metadata
|
||||||
// These properties represent metadata about the message in the mail
|
// These properties represent metadata about the message in the mail
|
||||||
|
@ -133,11 +148,11 @@ pub use import::*;
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct EmailObject {
|
pub struct EmailObject {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub id: Id,
|
pub id: Id<EmailObject>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub blob_id: String,
|
pub blob_id: Id<BlobObject>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub mailbox_ids: HashMap<Id, bool>,
|
pub mailbox_ids: HashMap<Id<MailboxObject>, bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -163,7 +178,7 @@ pub struct EmailObject {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub keywords: HashMap<String, bool>,
|
pub keywords: HashMap<String, bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub attached_emails: Option<Id>,
|
pub attached_emails: Option<Id<BlobObject>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub attachments: Vec<Value>,
|
pub attachments: Vec<Value>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -182,7 +197,7 @@ pub struct EmailObject {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub text_body: Vec<TextBody>,
|
pub text_body: Vec<TextBody>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub thread_id: Id,
|
pub thread_id: Id<ThreadObject>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub extra: HashMap<String, Value>,
|
pub extra: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
|
@ -313,9 +328,7 @@ impl std::convert::From<EmailObject> for crate::Envelope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut h = DefaultHasher::new();
|
env.set_hash(t.id.into_hash());
|
||||||
h.write(t.id.as_bytes());
|
|
||||||
env.set_hash(h.finish());
|
|
||||||
env
|
env
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,7 +336,7 @@ impl std::convert::From<EmailObject> for crate::Envelope {
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct HtmlBody {
|
pub struct HtmlBody {
|
||||||
pub blob_id: Id,
|
pub blob_id: Id<BlobObject>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub charset: String,
|
pub charset: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -350,7 +363,7 @@ pub struct HtmlBody {
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TextBody {
|
pub struct TextBody {
|
||||||
pub blob_id: Id,
|
pub blob_id: Id<BlobObject>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub charset: String,
|
pub charset: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -381,12 +394,12 @@ impl Object for EmailObject {
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct EmailQueryResponse {
|
pub struct EmailQueryResponse {
|
||||||
pub account_id: Id,
|
pub account_id: Id<Account>,
|
||||||
pub can_calculate_changes: bool,
|
pub can_calculate_changes: bool,
|
||||||
pub collapse_threads: bool,
|
pub collapse_threads: bool,
|
||||||
// FIXME
|
// FIXME
|
||||||
pub filter: String,
|
pub filter: String,
|
||||||
pub ids: Vec<Id>,
|
pub ids: Vec<Id<EmailObject>>,
|
||||||
pub position: u64,
|
pub position: u64,
|
||||||
pub query_state: String,
|
pub query_state: String,
|
||||||
pub sort: Option<String>,
|
pub sort: Option<String>,
|
||||||
|
@ -469,9 +482,9 @@ impl EmailGet {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct EmailFilterCondition {
|
pub struct EmailFilterCondition {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub in_mailbox: Option<Id>,
|
pub in_mailbox: Option<Id<MailboxObject>>,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
pub in_mailbox_other_than: Vec<Id>,
|
pub in_mailbox_other_than: Vec<Id<MailboxObject>>,
|
||||||
#[serde(skip_serializing_if = "String::is_empty")]
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
pub before: UtcDate,
|
pub before: UtcDate,
|
||||||
#[serde(skip_serializing_if = "String::is_empty")]
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
@ -517,8 +530,8 @@ impl EmailFilterCondition {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
_impl!(in_mailbox: Option<Id>);
|
_impl!(in_mailbox: Option<Id<MailboxObject>>);
|
||||||
_impl!(in_mailbox_other_than: Vec<Id>);
|
_impl!(in_mailbox_other_than: Vec<Id<MailboxObject>>);
|
||||||
_impl!(before: UtcDate);
|
_impl!(before: UtcDate);
|
||||||
_impl!(after: UtcDate);
|
_impl!(after: UtcDate);
|
||||||
_impl!(min_size: Option<u64>);
|
_impl!(min_size: Option<u64>);
|
||||||
|
@ -728,7 +741,7 @@ fn test_jmap_query() {
|
||||||
|
|
||||||
let mut r = Filter::Condition(
|
let mut r = Filter::Condition(
|
||||||
EmailFilterCondition::new()
|
EmailFilterCondition::new()
|
||||||
.in_mailbox(Some(mailbox_id))
|
.in_mailbox(Some(mailbox_id.into()))
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
r &= f;
|
r &= f;
|
||||||
|
@ -737,7 +750,7 @@ fn test_jmap_query() {
|
||||||
|
|
||||||
let email_call: EmailQuery = EmailQuery::new(
|
let email_call: EmailQuery = EmailQuery::new(
|
||||||
Query::new()
|
Query::new()
|
||||||
.account_id("account_id".to_string())
|
.account_id("account_id".to_string().into())
|
||||||
.filter(Some(filter))
|
.filter(Some(filter))
|
||||||
.position(0),
|
.position(0),
|
||||||
)
|
)
|
||||||
|
|
|
@ -37,7 +37,7 @@ use serde_json::value::RawValue;
|
||||||
pub struct ImportCall {
|
pub struct ImportCall {
|
||||||
///accountId: "Id"
|
///accountId: "Id"
|
||||||
///The id of the account to use.
|
///The id of the account to use.
|
||||||
pub account_id: String,
|
pub account_id: Id<Account>,
|
||||||
///ifInState: "String|null"
|
///ifInState: "String|null"
|
||||||
///This is a state string as returned by the "Email/get" method. If
|
///This is a state string as returned by the "Email/get" method. If
|
||||||
///supplied, the string must match the current state of the account
|
///supplied, the string must match the current state of the account
|
||||||
|
@ -45,10 +45,10 @@ pub struct ImportCall {
|
||||||
///and a "stateMismatch" error returned. If null, any changes will
|
///and a "stateMismatch" error returned. If null, any changes will
|
||||||
///be applied to the current state.
|
///be applied to the current state.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub if_in_state: Option<String>,
|
pub if_in_state: Option<State<EmailObject>>,
|
||||||
///o emails: "Id[EmailImport]"
|
///o emails: "Id[EmailImport]"
|
||||||
///A map of creation id (client specified) to EmailImport objects.
|
///A map of creation id (client specified) to EmailImport objects.
|
||||||
pub emails: HashMap<Id, EmailImport>,
|
pub emails: HashMap<Id<EmailObject>, EmailImport>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
@ -56,11 +56,11 @@ pub struct ImportCall {
|
||||||
pub struct EmailImport {
|
pub struct EmailImport {
|
||||||
///o blobId: "Id"
|
///o blobId: "Id"
|
||||||
///The id of the blob containing the raw message [RFC5322].
|
///The id of the blob containing the raw message [RFC5322].
|
||||||
pub blob_id: String,
|
pub blob_id: Id<BlobObject>,
|
||||||
///o mailboxIds: "Id[Boolean]"
|
///o mailboxIds: "Id[Boolean]"
|
||||||
///The ids of the Mailboxes to assign this Email to. At least one
|
///The ids of the Mailboxes to assign this Email to. At least one
|
||||||
///Mailbox MUST be given.
|
///Mailbox MUST be given.
|
||||||
pub mailbox_ids: HashMap<Id, bool>,
|
pub mailbox_ids: HashMap<Id<MailboxObject>, bool>,
|
||||||
///o keywords: "String[Boolean]" (default: {})
|
///o keywords: "String[Boolean]" (default: {})
|
||||||
///The keywords to apply to the Email.
|
///The keywords to apply to the Email.
|
||||||
pub keywords: HashMap<String, bool>,
|
pub keywords: HashMap<String, bool>,
|
||||||
|
@ -74,7 +74,7 @@ pub struct EmailImport {
|
||||||
impl ImportCall {
|
impl ImportCall {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
account_id: String::new(),
|
account_id: Id::new(),
|
||||||
if_in_state: None,
|
if_in_state: None,
|
||||||
emails: HashMap::default(),
|
emails: HashMap::default(),
|
||||||
}
|
}
|
||||||
|
@ -85,10 +85,10 @@ impl ImportCall {
|
||||||
///
|
///
|
||||||
/// The id of the account to use.
|
/// The id of the account to use.
|
||||||
///
|
///
|
||||||
account_id: String
|
account_id: Id<Account>
|
||||||
);
|
);
|
||||||
_impl!(if_in_state: Option<String>);
|
_impl!(if_in_state: Option<State<EmailObject>>);
|
||||||
_impl!(emails: HashMap<Id, EmailImport>);
|
_impl!(emails: HashMap<Id<EmailObject>, EmailImport>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Method<EmailObject> for ImportCall {
|
impl Method<EmailObject> for ImportCall {
|
||||||
|
@ -98,15 +98,15 @@ impl Method<EmailObject> for ImportCall {
|
||||||
impl EmailImport {
|
impl EmailImport {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
blob_id: String::new(),
|
blob_id: Id::new(),
|
||||||
mailbox_ids: HashMap::default(),
|
mailbox_ids: HashMap::default(),
|
||||||
keywords: HashMap::default(),
|
keywords: HashMap::default(),
|
||||||
received_at: None,
|
received_at: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_impl!(blob_id: String);
|
_impl!(blob_id: Id<BlobObject>);
|
||||||
_impl!(mailbox_ids: HashMap<Id, bool>);
|
_impl!(mailbox_ids: HashMap<Id<MailboxObject>, bool>);
|
||||||
_impl!(keywords: HashMap<String, bool>);
|
_impl!(keywords: HashMap<String, bool>);
|
||||||
_impl!(received_at: Option<String>);
|
_impl!(received_at: Option<String>);
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ pub enum ImportError {
|
||||||
///the SetError object with the id of the existing Email. If duplicates
|
///the SetError object with the id of the existing Email. If duplicates
|
||||||
///are allowed, the newly created Email object MUST have a separate id
|
///are allowed, the newly created Email object MUST have a separate id
|
||||||
///and independent mutable properties to the existing object.
|
///and independent mutable properties to the existing object.
|
||||||
existing_id: Id,
|
existing_id: Id<EmailObject>,
|
||||||
},
|
},
|
||||||
///If the "blobId", "mailboxIds", or "keywords" properties are invalid
|
///If the "blobId", "mailboxIds", or "keywords" properties are invalid
|
||||||
///(e.g., missing, wrong type, id not found), the server MUST reject the
|
///(e.g., missing, wrong type, id not found), the server MUST reject the
|
||||||
|
@ -155,30 +155,30 @@ pub enum ImportError {
|
||||||
pub struct ImportResponse {
|
pub struct ImportResponse {
|
||||||
///o accountId: "Id"
|
///o accountId: "Id"
|
||||||
///The id of the account used for this call.
|
///The id of the account used for this call.
|
||||||
pub account_id: Id,
|
pub account_id: Id<Account>,
|
||||||
|
|
||||||
///o oldState: "String|null"
|
///o oldState: "String|null"
|
||||||
///The state string that would have been returned by "Email/get" on
|
///The state string that would have been returned by "Email/get" on
|
||||||
///this account before making the requested changes, or null if the
|
///this account before making the requested changes, or null if the
|
||||||
///server doesn't know what the previous state string was.
|
///server doesn't know what the previous state string was.
|
||||||
pub old_state: Option<String>,
|
pub old_state: Option<State<EmailObject>>,
|
||||||
|
|
||||||
///o newState: "String"
|
///o newState: "String"
|
||||||
///The state string that will now be returned by "Email/get" on this
|
///The state string that will now be returned by "Email/get" on this
|
||||||
///account.
|
///account.
|
||||||
pub new_state: Option<String>,
|
pub new_state: Option<State<EmailObject>>,
|
||||||
|
|
||||||
///o created: "Id[Email]|null"
|
///o created: "Id[Email]|null"
|
||||||
///A map of the creation id to an object containing the "id",
|
///A map of the creation id to an object containing the "id",
|
||||||
///"blobId", "threadId", and "size" properties for each successfully
|
///"blobId", "threadId", and "size" properties for each successfully
|
||||||
///imported Email, or null if none.
|
///imported Email, or null if none.
|
||||||
pub created: HashMap<Id, ImportEmailResult>,
|
pub created: HashMap<Id<EmailObject>, ImportEmailResult>,
|
||||||
|
|
||||||
///o notCreated: "Id[SetError]|null"
|
///o notCreated: "Id[SetError]|null"
|
||||||
///A map of the creation id to a SetError object for each Email that
|
///A map of the creation id to a SetError object for each Email that
|
||||||
///failed to be created, or null if all successful. The possible
|
///failed to be created, or null if all successful. The possible
|
||||||
///errors are defined above.
|
///errors are defined above.
|
||||||
pub not_created: HashMap<Id, ImportError>,
|
pub not_created: HashMap<Id<EmailObject>, ImportError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::TryFrom<&RawValue> for ImportResponse {
|
impl std::convert::TryFrom<&RawValue> for ImportResponse {
|
||||||
|
@ -193,8 +193,8 @@ impl std::convert::TryFrom<&RawValue> for ImportResponse {
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ImportEmailResult {
|
pub struct ImportEmailResult {
|
||||||
pub id: Id,
|
pub id: Id<EmailObject>,
|
||||||
pub blob_id: Id,
|
pub blob_id: Id<BlobObject>,
|
||||||
pub thread_id: Id,
|
pub thread_id: Id<ThreadObject>,
|
||||||
pub size: usize,
|
pub size: usize,
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,22 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
impl Id<MailboxObject> {
|
||||||
|
pub fn into_hash(&self) -> MailboxHash {
|
||||||
|
let mut h = DefaultHasher::new();
|
||||||
|
h.write(self.inner.as_bytes());
|
||||||
|
h.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MailboxObject {
|
pub struct MailboxObject {
|
||||||
pub id: String,
|
pub id: Id<MailboxObject>,
|
||||||
pub is_subscribed: bool,
|
pub is_subscribed: bool,
|
||||||
pub my_rights: JmapRights,
|
pub my_rights: JmapRights,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub parent_id: Option<String>,
|
pub parent_id: Option<Id<MailboxObject>>,
|
||||||
pub role: Option<String>,
|
pub role: Option<String>,
|
||||||
pub sort_order: u64,
|
pub sort_order: u64,
|
||||||
pub total_emails: u64,
|
pub total_emails: u64,
|
||||||
|
|
|
@ -25,7 +25,6 @@ use serde::Serialize;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
pub type Id = String;
|
|
||||||
pub type UtcDate = String;
|
pub type UtcDate = String;
|
||||||
|
|
||||||
use super::rfc8620::Object;
|
use super::rfc8620::Object;
|
||||||
|
@ -125,7 +124,8 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
|
||||||
unread_emails,
|
unread_emails,
|
||||||
unread_threads,
|
unread_threads,
|
||||||
} = r;
|
} = r;
|
||||||
let hash = crate::get_path_hash!(&name);
|
let hash = id.into_hash();
|
||||||
|
let parent_hash = parent_id.clone().map(|id| id.into_hash());
|
||||||
(
|
(
|
||||||
hash,
|
hash,
|
||||||
JmapMailbox {
|
JmapMailbox {
|
||||||
|
@ -137,6 +137,7 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
|
||||||
is_subscribed,
|
is_subscribed,
|
||||||
my_rights,
|
my_rights,
|
||||||
parent_id,
|
parent_id,
|
||||||
|
parent_hash,
|
||||||
role,
|
role,
|
||||||
usage: Default::default(),
|
usage: Default::default(),
|
||||||
sort_order,
|
sort_order,
|
||||||
|
@ -150,10 +151,13 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_message_list(conn: &JmapConnection, mailbox: &JmapMailbox) -> Result<Vec<String>> {
|
pub async fn get_message_list(
|
||||||
|
conn: &JmapConnection,
|
||||||
|
mailbox: &JmapMailbox,
|
||||||
|
) -> Result<Vec<Id<EmailObject>>> {
|
||||||
let email_call: EmailQuery = EmailQuery::new(
|
let email_call: EmailQuery = EmailQuery::new(
|
||||||
Query::new()
|
Query::new()
|
||||||
.account_id(conn.mail_account_id().to_string())
|
.account_id(conn.mail_account_id().clone())
|
||||||
.filter(Some(Filter::Condition(
|
.filter(Some(Filter::Condition(
|
||||||
EmailFilterCondition::new()
|
EmailFilterCondition::new()
|
||||||
.in_mailbox(Some(mailbox.id.clone()))
|
.in_mailbox(Some(mailbox.id.clone()))
|
||||||
|
@ -213,7 +217,7 @@ pub async fn fetch(
|
||||||
let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone();
|
let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone();
|
||||||
let email_query_call: EmailQuery = EmailQuery::new(
|
let email_query_call: EmailQuery = EmailQuery::new(
|
||||||
Query::new()
|
Query::new()
|
||||||
.account_id(conn.mail_account_id().to_string())
|
.account_id(conn.mail_account_id().clone())
|
||||||
.filter(Some(Filter::Condition(
|
.filter(Some(Filter::Condition(
|
||||||
EmailFilterCondition::new()
|
EmailFilterCondition::new()
|
||||||
.in_mailbox(Some(mailbox_id))
|
.in_mailbox(Some(mailbox_id))
|
||||||
|
@ -232,7 +236,7 @@ pub async fn fetch(
|
||||||
prev_seq,
|
prev_seq,
|
||||||
EmailQuery::RESULT_FIELD_IDS,
|
EmailQuery::RESULT_FIELD_IDS,
|
||||||
)))
|
)))
|
||||||
.account_id(conn.mail_account_id().to_string()),
|
.account_id(conn.mail_account_id().clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
req.add_call(&email_call);
|
req.add_call(&email_call);
|
||||||
|
@ -248,22 +252,15 @@ pub async fn fetch(
|
||||||
let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
||||||
let GetResponse::<EmailObject> { list, state, .. } = e;
|
let GetResponse::<EmailObject> { list, state, .. } = e;
|
||||||
{
|
{
|
||||||
let v = conn
|
let (is_empty, is_equal) = {
|
||||||
.store
|
let current_state_lck = conn.store.email_state.lock().unwrap();
|
||||||
.object_set_states
|
(current_state_lck.is_empty(), *current_state_lck != state)
|
||||||
.lock()
|
};
|
||||||
.unwrap()
|
if is_empty {
|
||||||
.get(&EmailObject::NAME)
|
|
||||||
.map(|prev_state| *prev_state == state);
|
|
||||||
if let Some(false) = v {
|
|
||||||
conn.email_changes().await?;
|
|
||||||
} else {
|
|
||||||
debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
|
debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
|
||||||
conn.store
|
*conn.store.email_state.lock().unwrap() = state;
|
||||||
.object_set_states
|
} else if !is_equal {
|
||||||
.lock()
|
conn.email_changes().await?;
|
||||||
.unwrap()
|
|
||||||
.insert(EmailObject::NAME, state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut ret = Vec::with_capacity(list.len());
|
let mut ret = Vec::with_capacity(list.len());
|
||||||
|
|
|
@ -19,12 +19,12 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::Id;
|
|
||||||
use crate::email::parser::BytesExt;
|
use crate::email::parser::BytesExt;
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||||
use serde_json::{value::RawValue, Value};
|
use serde_json::{value::RawValue, Value};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
mod filters;
|
mod filters;
|
||||||
pub use filters::*;
|
pub use filters::*;
|
||||||
|
@ -39,23 +39,189 @@ pub trait Object {
|
||||||
const NAME: &'static str;
|
const NAME: &'static str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct Id<OBJ> {
|
||||||
|
pub inner: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub _ph: PhantomData<fn() -> OBJ>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ: Object> core::fmt::Debug for Id<OBJ> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
f.debug_tuple(&format!("Id<{}>", OBJ::NAME))
|
||||||
|
.field(&self.inner)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for Id<String> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("Id<Any>").field(&self.inner).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//, Hash, Eq, PartialEq, Default)]
|
||||||
|
impl<OBJ> Clone for Id<OBJ> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Id {
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
_ph: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> std::cmp::Eq for Id<OBJ> {}
|
||||||
|
|
||||||
|
impl<OBJ> std::cmp::PartialEq for Id<OBJ> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.inner == other.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> Hash for Id<OBJ> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.inner.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> Default for Id<OBJ> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> From<String> for Id<OBJ> {
|
||||||
|
fn from(inner: String) -> Self {
|
||||||
|
Id {
|
||||||
|
inner,
|
||||||
|
_ph: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> core::fmt::Display for Id<OBJ> {
|
||||||
|
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
core::fmt::Display::fmt(&self.inner, fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> Id<OBJ> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: String::new(),
|
||||||
|
_ph: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.inner.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.inner.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.inner.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct State<OBJ> {
|
||||||
|
pub inner: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub _ph: PhantomData<fn() -> OBJ>,
|
||||||
|
}
|
||||||
|
|
||||||
|
//, Hash, Eq, PartialEq, Default)]
|
||||||
|
impl<OBJ> Clone for State<OBJ> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
State {
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
_ph: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> std::cmp::Eq for State<OBJ> {}
|
||||||
|
|
||||||
|
impl<OBJ> std::cmp::PartialEq for State<OBJ> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.inner == other.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> Hash for State<OBJ> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.inner.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> Default for State<OBJ> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> From<String> for State<OBJ> {
|
||||||
|
fn from(inner: String) -> Self {
|
||||||
|
State {
|
||||||
|
inner,
|
||||||
|
_ph: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> core::fmt::Display for State<OBJ> {
|
||||||
|
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
core::fmt::Display::fmt(&self.inner, fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OBJ> State<OBJ> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: String::new(),
|
||||||
|
_ph: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.inner.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.inner.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.inner.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
#[derive(Deserialize, Serialize, Debug, 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>,
|
||||||
pub accounts: HashMap<Id, Account>,
|
pub accounts: HashMap<Id<Account>, Account>,
|
||||||
pub primary_accounts: HashMap<String, Id>,
|
pub primary_accounts: HashMap<String, Id<Account>>,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub api_url: String,
|
pub api_url: String,
|
||||||
pub download_url: String,
|
pub download_url: String,
|
||||||
|
|
||||||
pub upload_url: String,
|
pub upload_url: String,
|
||||||
pub event_source_url: String,
|
pub event_source_url: String,
|
||||||
pub state: String,
|
pub state: State<JmapSession>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub extra_properties: HashMap<String, Value>,
|
pub extra_properties: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Object for JmapSession {
|
||||||
|
const NAME: &'static str = "Session";
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CapabilitiesObject {
|
pub struct CapabilitiesObject {
|
||||||
|
@ -88,6 +254,17 @@ pub struct Account {
|
||||||
extra_properties: HashMap<String, Value>,
|
extra_properties: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Object for Account {
|
||||||
|
const NAME: &'static str = "Account";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BlobObject;
|
||||||
|
|
||||||
|
impl Object for BlobObject {
|
||||||
|
const NAME: &'static str = "Blob";
|
||||||
|
}
|
||||||
|
|
||||||
/// #`get`
|
/// #`get`
|
||||||
///
|
///
|
||||||
/// Objects of type `Foo` are fetched via a call to `Foo/get`.
|
/// Objects of type `Foo` are fetched via a call to `Foo/get`.
|
||||||
|
@ -104,11 +281,10 @@ pub struct Get<OBJ: Object>
|
||||||
where
|
where
|
||||||
OBJ: std::fmt::Debug + Serialize,
|
OBJ: std::fmt::Debug + Serialize,
|
||||||
{
|
{
|
||||||
#[serde(skip_serializing_if = "String::is_empty")]
|
pub account_id: Id<Account>,
|
||||||
pub account_id: String,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub ids: Option<JmapArgument<Vec<String>>>,
|
pub ids: Option<JmapArgument<Vec<Id<OBJ>>>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub properties: Option<Vec<String>>,
|
pub properties: Option<Vec<String>>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
@ -121,7 +297,7 @@ where
|
||||||
{
|
{
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
account_id: String::new(),
|
account_id: Id::new(),
|
||||||
ids: None,
|
ids: None,
|
||||||
properties: None,
|
properties: None,
|
||||||
_ph: PhantomData,
|
_ph: PhantomData,
|
||||||
|
@ -132,7 +308,7 @@ where
|
||||||
///
|
///
|
||||||
/// The id of the account to use.
|
/// The id of the account to use.
|
||||||
///
|
///
|
||||||
account_id: String
|
account_id: Id<Account>
|
||||||
);
|
);
|
||||||
_impl!(
|
_impl!(
|
||||||
/// - ids: `Option<JmapArgument<Vec<String>>>`
|
/// - ids: `Option<JmapArgument<Vec<String>>>`
|
||||||
|
@ -142,7 +318,7 @@ where
|
||||||
/// type and the number of records does not exceed the
|
/// type and the number of records does not exceed the
|
||||||
/// "max_objects_in_get" limit.
|
/// "max_objects_in_get" limit.
|
||||||
///
|
///
|
||||||
ids: Option<JmapArgument<Vec<String>>>
|
ids: Option<JmapArgument<Vec<Id<OBJ>>>>
|
||||||
);
|
);
|
||||||
_impl!(
|
_impl!(
|
||||||
/// - properties: Option<Vec<String>>
|
/// - properties: Option<Vec<String>>
|
||||||
|
@ -218,20 +394,19 @@ pub struct MethodResponse<'a> {
|
||||||
#[serde(borrow)]
|
#[serde(borrow)]
|
||||||
pub method_responses: Vec<&'a RawValue>,
|
pub method_responses: Vec<&'a RawValue>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub created_ids: HashMap<Id, Id>,
|
pub created_ids: HashMap<Id<String>, Id<String>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub session_state: String,
|
pub session_state: State<JmapSession>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GetResponse<OBJ: Object> {
|
pub struct GetResponse<OBJ: Object> {
|
||||||
#[serde(skip_serializing_if = "String::is_empty")]
|
pub account_id: Id<Account>,
|
||||||
pub account_id: String,
|
#[serde(default = "State::default")]
|
||||||
#[serde(default)]
|
pub state: State<OBJ>,
|
||||||
pub state: String,
|
|
||||||
pub list: Vec<OBJ>,
|
pub list: Vec<OBJ>,
|
||||||
pub not_found: Vec<String>,
|
pub not_found: Vec<Id<OBJ>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for GetResponse<OBJ> {
|
impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for GetResponse<OBJ> {
|
||||||
|
@ -244,10 +419,10 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for GetRes
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<OBJ: Object> GetResponse<OBJ> {
|
impl<OBJ: Object> GetResponse<OBJ> {
|
||||||
_impl!(get_mut account_id_mut, account_id: String);
|
_impl!(get_mut account_id_mut, account_id: Id<Account>);
|
||||||
_impl!(get_mut state_mut, state: String);
|
_impl!(get_mut state_mut, state: State<OBJ>);
|
||||||
_impl!(get_mut list_mut, list: Vec<OBJ>);
|
_impl!(get_mut list_mut, list: Vec<OBJ>);
|
||||||
_impl!(get_mut not_found_mut, not_found: Vec<String>);
|
_impl!(get_mut not_found_mut, not_found: Vec<Id<OBJ>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
@ -264,7 +439,7 @@ pub struct Query<F: FilterTrait<OBJ>, OBJ: Object>
|
||||||
where
|
where
|
||||||
OBJ: std::fmt::Debug + Serialize,
|
OBJ: std::fmt::Debug + Serialize,
|
||||||
{
|
{
|
||||||
account_id: String,
|
account_id: Id<Account>,
|
||||||
filter: Option<F>,
|
filter: Option<F>,
|
||||||
sort: Option<Comparator<OBJ>>,
|
sort: Option<Comparator<OBJ>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -288,7 +463,7 @@ where
|
||||||
{
|
{
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
account_id: String::new(),
|
account_id: Id::new(),
|
||||||
filter: None,
|
filter: None,
|
||||||
sort: None,
|
sort: None,
|
||||||
position: 0,
|
position: 0,
|
||||||
|
@ -300,7 +475,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_impl!(account_id: String);
|
_impl!(account_id: Id<Account>);
|
||||||
_impl!(filter: Option<F>);
|
_impl!(filter: Option<F>);
|
||||||
_impl!(sort: Option<Comparator<OBJ>>);
|
_impl!(sort: Option<Comparator<OBJ>>);
|
||||||
_impl!(position: u64);
|
_impl!(position: u64);
|
||||||
|
@ -325,12 +500,11 @@ pub fn bool_true() -> bool {
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct QueryResponse<OBJ: Object> {
|
pub struct QueryResponse<OBJ: Object> {
|
||||||
#[serde(skip_serializing_if = "String::is_empty", default)]
|
pub account_id: Id<Account>,
|
||||||
pub account_id: String,
|
|
||||||
pub query_state: String,
|
pub query_state: String,
|
||||||
pub can_calculate_changes: bool,
|
pub can_calculate_changes: bool,
|
||||||
pub position: u64,
|
pub position: u64,
|
||||||
pub ids: Vec<Id>,
|
pub ids: Vec<Id<OBJ>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub total: u64,
|
pub total: u64,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -349,7 +523,7 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for QueryR
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<OBJ: Object> QueryResponse<OBJ> {
|
impl<OBJ: Object> QueryResponse<OBJ> {
|
||||||
_impl!(get_mut ids_mut, ids: Vec<Id>);
|
_impl!(get_mut ids_mut, ids: Vec<Id<OBJ>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ResultField<M: Method<OBJ>, OBJ: Object> {
|
pub struct ResultField<M: Method<OBJ>, OBJ: Object> {
|
||||||
|
@ -410,9 +584,8 @@ pub struct Changes<OBJ: Object>
|
||||||
where
|
where
|
||||||
OBJ: std::fmt::Debug + Serialize,
|
OBJ: std::fmt::Debug + Serialize,
|
||||||
{
|
{
|
||||||
#[serde(skip_serializing_if = "String::is_empty")]
|
pub account_id: Id<Account>,
|
||||||
pub account_id: String,
|
pub since_state: State<OBJ>,
|
||||||
pub since_state: String,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub max_changes: Option<u64>,
|
pub max_changes: Option<u64>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
@ -425,8 +598,8 @@ where
|
||||||
{
|
{
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
account_id: String::new(),
|
account_id: Id::new(),
|
||||||
since_state: String::new(),
|
since_state: State::new(),
|
||||||
max_changes: None,
|
max_changes: None,
|
||||||
_ph: PhantomData,
|
_ph: PhantomData,
|
||||||
}
|
}
|
||||||
|
@ -436,7 +609,7 @@ where
|
||||||
///
|
///
|
||||||
/// The id of the account to use.
|
/// The id of the account to use.
|
||||||
///
|
///
|
||||||
account_id: String
|
account_id: Id<Account>
|
||||||
);
|
);
|
||||||
_impl!(
|
_impl!(
|
||||||
/// - since_state: "String"
|
/// - since_state: "String"
|
||||||
|
@ -446,7 +619,7 @@ where
|
||||||
/// state.
|
/// state.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
since_state: String
|
since_state: State<OBJ>
|
||||||
);
|
);
|
||||||
_impl!(
|
_impl!(
|
||||||
/// - max_changes: "UnsignedInt|null"
|
/// - max_changes: "UnsignedInt|null"
|
||||||
|
@ -463,14 +636,13 @@ where
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChangesResponse<OBJ: Object> {
|
pub struct ChangesResponse<OBJ: Object> {
|
||||||
#[serde(skip_serializing_if = "String::is_empty")]
|
pub account_id: Id<Account>,
|
||||||
pub account_id: String,
|
pub old_state: State<OBJ>,
|
||||||
pub old_state: String,
|
pub new_state: State<OBJ>,
|
||||||
pub new_state: String,
|
|
||||||
pub has_more_changes: bool,
|
pub has_more_changes: bool,
|
||||||
pub created: Vec<Id>,
|
pub created: Vec<Id<OBJ>>,
|
||||||
pub updated: Vec<Id>,
|
pub updated: Vec<Id<OBJ>>,
|
||||||
pub destroyed: Vec<Id>,
|
pub destroyed: Vec<Id<OBJ>>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub _ph: PhantomData<fn() -> OBJ>,
|
pub _ph: PhantomData<fn() -> OBJ>,
|
||||||
}
|
}
|
||||||
|
@ -485,13 +657,13 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for Change
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<OBJ: Object> ChangesResponse<OBJ> {
|
impl<OBJ: Object> ChangesResponse<OBJ> {
|
||||||
_impl!(get_mut account_id_mut, account_id: String);
|
_impl!(get_mut account_id_mut, account_id: Id<Account>);
|
||||||
_impl!(get_mut old_state_mut, old_state: String);
|
_impl!(get_mut old_state_mut, old_state: State<OBJ>);
|
||||||
_impl!(get_mut new_state_mut, new_state: String);
|
_impl!(get_mut new_state_mut, new_state: State<OBJ>);
|
||||||
_impl!(get has_more_changes, has_more_changes: bool);
|
_impl!(get has_more_changes, has_more_changes: bool);
|
||||||
_impl!(get_mut created_mut, created: Vec<String>);
|
_impl!(get_mut created_mut, created: Vec<Id<OBJ>>);
|
||||||
_impl!(get_mut updated_mut, updated: Vec<String>);
|
_impl!(get_mut updated_mut, updated: Vec<Id<OBJ>>);
|
||||||
_impl!(get_mut destroyed_mut, destroyed: Vec<String>);
|
_impl!(get_mut destroyed_mut, destroyed: Vec<Id<OBJ>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
///#`set`
|
///#`set`
|
||||||
|
@ -508,11 +680,10 @@ pub struct Set<OBJ: Object>
|
||||||
where
|
where
|
||||||
OBJ: std::fmt::Debug + Serialize,
|
OBJ: std::fmt::Debug + Serialize,
|
||||||
{
|
{
|
||||||
#[serde(skip_serializing_if = "String::is_empty")]
|
|
||||||
///o accountId: "Id"
|
///o accountId: "Id"
|
||||||
///
|
///
|
||||||
/// The id of the account to use.
|
/// The id of the account to use.
|
||||||
pub account_id: String,
|
pub account_id: Id<Account>,
|
||||||
///o ifInState: "String|null"
|
///o ifInState: "String|null"
|
||||||
///
|
///
|
||||||
/// This is a state string as returned by the "Foo/get" method
|
/// This is a state string as returned by the "Foo/get" method
|
||||||
|
@ -521,7 +692,7 @@ where
|
||||||
/// otherwise, the method will be aborted and a "stateMismatch" error
|
/// otherwise, the method will be aborted and a "stateMismatch" error
|
||||||
/// returned. If null, any changes will be applied to the current
|
/// returned. If null, any changes will be applied to the current
|
||||||
/// state.
|
/// state.
|
||||||
pub if_in_state: Option<String>,
|
pub if_in_state: Option<State<OBJ>>,
|
||||||
///o create: "Id[Foo]|null"
|
///o create: "Id[Foo]|null"
|
||||||
///
|
///
|
||||||
/// A map of a *creation id* (a temporary id set by the client) to Foo
|
/// A map of a *creation id* (a temporary id set by the client) to Foo
|
||||||
|
@ -533,7 +704,7 @@ where
|
||||||
/// The client MUST omit any properties that may only be set by the
|
/// The client MUST omit any properties that may only be set by the
|
||||||
/// server (for example, the "id" property on most object types).
|
/// server (for example, the "id" property on most object types).
|
||||||
///
|
///
|
||||||
pub create: Option<HashMap<Id, OBJ>>,
|
pub create: Option<HashMap<Id<OBJ>, OBJ>>,
|
||||||
///o update: "Id[PatchObject]|null"
|
///o update: "Id[PatchObject]|null"
|
||||||
///
|
///
|
||||||
/// A map of an id to a Patch object to apply to the current Foo
|
/// A map of an id to a Patch object to apply to the current Foo
|
||||||
|
@ -577,12 +748,12 @@ where
|
||||||
/// is also a valid PatchObject. The client may choose to optimise
|
/// is also a valid PatchObject. The client may choose to optimise
|
||||||
/// network usage by just sending the diff or may send the whole
|
/// network usage by just sending the diff or may send the whole
|
||||||
/// object; the server processes it the same either way.
|
/// object; the server processes it the same either way.
|
||||||
pub update: Option<HashMap<Id, Value>>,
|
pub update: Option<HashMap<Id<OBJ>, Value>>,
|
||||||
///o destroy: "Id[]|null"
|
///o destroy: "Id[]|null"
|
||||||
///
|
///
|
||||||
/// A list of ids for Foo objects to permanently delete, or null if no
|
/// A list of ids for Foo objects to permanently delete, or null if no
|
||||||
/// objects are to be destroyed.
|
/// objects are to be destroyed.
|
||||||
pub destroy: Option<Vec<Id>>,
|
pub destroy: Option<Vec<Id<OBJ>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<OBJ: Object> Set<OBJ>
|
impl<OBJ: Object> Set<OBJ>
|
||||||
|
@ -591,14 +762,14 @@ where
|
||||||
{
|
{
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
account_id: String::new(),
|
account_id: Id::new(),
|
||||||
if_in_state: None,
|
if_in_state: None,
|
||||||
create: None,
|
create: None,
|
||||||
update: None,
|
update: None,
|
||||||
destroy: None,
|
destroy: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_impl!(account_id: String);
|
_impl!(account_id: Id<Account>);
|
||||||
_impl!(
|
_impl!(
|
||||||
///o ifInState: "String|null"
|
///o ifInState: "String|null"
|
||||||
///
|
///
|
||||||
|
@ -608,9 +779,9 @@ where
|
||||||
/// otherwise, the method will be aborted and a "stateMismatch" error
|
/// otherwise, the method will be aborted and a "stateMismatch" error
|
||||||
/// returned. If null, any changes will be applied to the current
|
/// returned. If null, any changes will be applied to the current
|
||||||
/// state.
|
/// state.
|
||||||
if_in_state: Option<String>
|
if_in_state: Option<State<OBJ>>
|
||||||
);
|
);
|
||||||
_impl!(update: Option<HashMap<Id, Value>>);
|
_impl!(update: Option<HashMap<Id<OBJ>, Value>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
@ -619,17 +790,17 @@ pub struct SetResponse<OBJ: Object> {
|
||||||
///o accountId: "Id"
|
///o accountId: "Id"
|
||||||
///
|
///
|
||||||
/// The id of the account used for the call.
|
/// The id of the account used for the call.
|
||||||
pub account_id: String,
|
pub account_id: Id<Account>,
|
||||||
///o oldState: "String|null"
|
///o oldState: "String|null"
|
||||||
///
|
///
|
||||||
/// The state string that would have been returned by "Foo/get" before
|
/// The state string that would have been returned by "Foo/get" before
|
||||||
/// making the requested changes, or null if the server doesn't know
|
/// making the requested changes, or null if the server doesn't know
|
||||||
/// what the previous state string was.
|
/// what the previous state string was.
|
||||||
pub old_state: String,
|
pub old_state: State<OBJ>,
|
||||||
///o newState: "String"
|
///o newState: "String"
|
||||||
///
|
///
|
||||||
/// The state string that will now be returned by "Foo/get".
|
/// The state string that will now be returned by "Foo/get".
|
||||||
pub new_state: String,
|
pub new_state: State<OBJ>,
|
||||||
///o created: "Id[Foo]|null"
|
///o created: "Id[Foo]|null"
|
||||||
///
|
///
|
||||||
/// A map of the creation id to an object containing any properties of
|
/// A map of the creation id to an object containing any properties of
|
||||||
|
@ -639,7 +810,7 @@ pub struct SetResponse<OBJ: Object> {
|
||||||
/// and thus set to a default by the server.
|
/// and thus set to a default by the server.
|
||||||
///
|
///
|
||||||
/// This argument is null if no Foo objects were successfully created.
|
/// This argument is null if no Foo objects were successfully created.
|
||||||
pub created: Option<HashMap<Id, OBJ>>,
|
pub created: Option<HashMap<Id<OBJ>, OBJ>>,
|
||||||
///o updated: "Id[Foo|null]|null"
|
///o updated: "Id[Foo|null]|null"
|
||||||
///
|
///
|
||||||
/// The keys in this map are the ids of all Foos that were
|
/// The keys in this map are the ids of all Foos that were
|
||||||
|
@ -651,12 +822,12 @@ pub struct SetResponse<OBJ: Object> {
|
||||||
/// any changes to server-set or computed properties.
|
/// any changes to server-set or computed properties.
|
||||||
///
|
///
|
||||||
/// This argument is null if no Foo objects were successfully updated.
|
/// This argument is null if no Foo objects were successfully updated.
|
||||||
pub updated: Option<HashMap<Id, Option<OBJ>>>,
|
pub updated: Option<HashMap<Id<OBJ>, Option<OBJ>>>,
|
||||||
///o destroyed: "Id[]|null"
|
///o destroyed: "Id[]|null"
|
||||||
///
|
///
|
||||||
/// A list of Foo ids for records that were successfully destroyed, or
|
/// A list of Foo ids for records that were successfully destroyed, or
|
||||||
/// null if none.
|
/// null if none.
|
||||||
pub destroyed: Option<Vec<Id>>,
|
pub destroyed: Option<Vec<Id<OBJ>>>,
|
||||||
///o notCreated: "Id[SetError]|null"
|
///o notCreated: "Id[SetError]|null"
|
||||||
///
|
///
|
||||||
/// A map of the creation id to a SetError object for each record that
|
/// A map of the creation id to a SetError object for each record that
|
||||||
|
@ -760,8 +931,8 @@ impl core::fmt::Display for SetError {
|
||||||
|
|
||||||
pub fn download_request_format(
|
pub fn download_request_format(
|
||||||
session: &JmapSession,
|
session: &JmapSession,
|
||||||
account_id: &Id,
|
account_id: &Id<Account>,
|
||||||
blob_id: &Id,
|
blob_id: &Id<BlobObject>,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
) -> String {
|
) -> String {
|
||||||
// https://jmap.fastmail.com/download/{accountId}/{blobId}/{name}
|
// https://jmap.fastmail.com/download/{accountId}/{blobId}/{name}
|
||||||
|
@ -777,10 +948,10 @@ pub fn download_request_format(
|
||||||
ret.push_str(&session.download_url[prev_pos..prev_pos + pos]);
|
ret.push_str(&session.download_url[prev_pos..prev_pos + pos]);
|
||||||
prev_pos += pos;
|
prev_pos += pos;
|
||||||
if session.download_url[prev_pos..].starts_with("{accountId}") {
|
if session.download_url[prev_pos..].starts_with("{accountId}") {
|
||||||
ret.push_str(account_id);
|
ret.push_str(account_id.as_str());
|
||||||
prev_pos += "{accountId}".len();
|
prev_pos += "{accountId}".len();
|
||||||
} else if session.download_url[prev_pos..].starts_with("{blobId}") {
|
} else if session.download_url[prev_pos..].starts_with("{blobId}") {
|
||||||
ret.push_str(blob_id);
|
ret.push_str(blob_id.as_str());
|
||||||
prev_pos += "{blobId}".len();
|
prev_pos += "{blobId}".len();
|
||||||
} else if session.download_url[prev_pos..].starts_with("{name}") {
|
} else if session.download_url[prev_pos..].starts_with("{name}") {
|
||||||
ret.push_str(name.as_ref().map(String::as_str).unwrap_or(""));
|
ret.push_str(name.as_ref().map(String::as_str).unwrap_or(""));
|
||||||
|
@ -793,7 +964,7 @@ pub fn download_request_format(
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upload_request_format(session: &JmapSession, account_id: &Id) -> String {
|
pub fn upload_request_format(session: &JmapSession, account_id: &Id<Account>) -> String {
|
||||||
//"uploadUrl": "https://jmap.fastmail.com/upload/{accountId}/",
|
//"uploadUrl": "https://jmap.fastmail.com/upload/{accountId}/",
|
||||||
let mut ret = String::with_capacity(session.upload_url.len() + account_id.len());
|
let mut ret = String::with_capacity(session.upload_url.len() + account_id.len());
|
||||||
let mut prev_pos = 0;
|
let mut prev_pos = 0;
|
||||||
|
@ -802,7 +973,7 @@ pub fn upload_request_format(session: &JmapSession, account_id: &Id) -> String {
|
||||||
ret.push_str(&session.upload_url[prev_pos..prev_pos + pos]);
|
ret.push_str(&session.upload_url[prev_pos..prev_pos + pos]);
|
||||||
prev_pos += pos;
|
prev_pos += pos;
|
||||||
if session.upload_url[prev_pos..].starts_with("{accountId}") {
|
if session.upload_url[prev_pos..].starts_with("{accountId}") {
|
||||||
ret.push_str(account_id);
|
ret.push_str(account_id.as_str());
|
||||||
prev_pos += "{accountId}".len();
|
prev_pos += "{accountId}".len();
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
@ -822,12 +993,12 @@ pub struct UploadResponse {
|
||||||
///o accountId: "Id"
|
///o accountId: "Id"
|
||||||
///
|
///
|
||||||
/// The id of the account used for the call.
|
/// The id of the account used for the call.
|
||||||
pub account_id: String,
|
pub account_id: Id<Account>,
|
||||||
///o blobId: "Id"
|
///o blobId: "Id"
|
||||||
///
|
///
|
||||||
///The id representing the binary data uploaded. The data for this id is immutable.
|
///The id representing the binary data uploaded. The data for this id is immutable.
|
||||||
///The id *only* refers to the binary data, not any metadata.
|
///The id *only* refers to the binary data, not any metadata.
|
||||||
pub blob_id: String,
|
pub blob_id: Id<BlobObject>,
|
||||||
///o type: "String"
|
///o type: "String"
|
||||||
///
|
///
|
||||||
///The media type of the file (as specified in [RFC6838],
|
///The media type of the file (as specified in [RFC6838],
|
||||||
|
|
Loading…
Reference in New Issue