diff options
Diffstat (limited to 'melib/src/backends/jmap/rfc8620.rs')
-rw-r--r-- | melib/src/backends/jmap/rfc8620.rs | 313 |
1 files changed, 242 insertions, 71 deletions
diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index 863221ee..d49bb593 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -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], |