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