diff --git a/Cargo.lock b/Cargo.lock index a42a61b2..75edbb87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1284,6 +1284,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "serde_path_to_error", "smallvec", "smol", "stderrlog", @@ -1934,6 +1935,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "sha1_smol" version = "1.0.0" diff --git a/meli/Cargo.toml b/meli/Cargo.toml index 22c058c4..8e35e9cf 100644 --- a/meli/Cargo.toml +++ b/meli/Cargo.toml @@ -71,7 +71,7 @@ regex = "1" tempfile = "3.3" [features] -default = ["sqlite3", "notmuch", "regexp", "smtp", "dbus-notifications", "gpgme", "cli-docs"] +default = ["sqlite3", "notmuch", "regexp", "smtp", "dbus-notifications", "gpgme", "cli-docs", "jmap"] notmuch = ["melib/notmuch_backend", ] jmap = ["melib/jmap_backend",] sqlite3 = ["melib/sqlite3"] diff --git a/meli/src/plugins/backend.rs b/meli/src/plugins/backend.rs index b8398170..b796f403 100644 --- a/meli/src/plugins/backend.rs +++ b/meli/src/plugins/backend.rs @@ -207,7 +207,9 @@ impl MailBackend for PluginBackend { &mut self, _name: String, ) -> ResultFuture<(MailboxHash, HashMap)> { - Err(Error::new("Creating a mailbox is currently unimplemented for plugins")) + Err(Error::new( + "Creating a mailbox is currently unimplemented for plugins", + )) } fn collection(&self) -> melib::Collection { self.collection.clone() diff --git a/melib/Cargo.toml b/melib/Cargo.toml index 24f40056..2ccac383 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -40,9 +40,10 @@ nom = { version = "7" } notify = { version = "4.0.15", optional = true } regex = { version = "1" } rusqlite = { version = "^0.28", default-features = false, optional = true } -serde = { version = "1.0.71", features = ["rc", ] } -serde_derive = "1.0.71" +serde = { version = "1.0", features = ["rc", ] } +serde_derive = "1.0" serde_json = { version = "1.0", features = ["raw_value",] } +serde_path_to_error = { version = "0.1" } smallvec = { version = "^1.5.0", features = ["serde", ] } smol = "1.0.0" diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 4a4802d5..0902ccc7 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -84,6 +84,22 @@ use objects::*; pub mod mailbox; use mailbox::*; +pub fn deserialize_from_str<'de, T: serde::de::Deserialize<'de>>(s: &'de str) -> Result { + let jd = &mut serde_json::Deserializer::from_str(s); + match serde_path_to_error::deserialize(jd) { + Ok(v) => Ok(v), + Err(err) => Err(Error::new(format!( + "BUG: Could not deserialize server JSON response properly, please report this!\nError \ + {} at {}. Reply from server: {}", + err, + err.path(), + &s + )) + .set_source(Some(Arc::new(err))) + .set_kind(ErrorKind::Bug)), + } +} + #[derive(Debug, Default)] pub struct EnvelopeCache { bytes: Option, @@ -430,15 +446,8 @@ impl MailBackend for JmapType { }; let res_text = res.text().await?; - let upload_response: UploadResponse = match serde_json::from_str(&res_text) { + let upload_response: UploadResponse = match deserialize_from_str(&res_text) { Err(err) => { - let err = Error::new(format!( - "BUG: Could not deserialize {} server JSON response properly, please \ - report this!\nReply from server: {}", - &conn.server_conf.server_url, &res_text - )) - .set_source(Some(Arc::new(err))) - .set_kind(ErrorKind::Bug); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); } @@ -467,23 +476,15 @@ impl MailBackend for JmapType { .await?; let res_text = res.text().await?; - let mut v: MethodResponse = match serde_json::from_str(&res_text) { + let mut v: MethodResponse = match deserialize_from_str(&res_text) { Err(err) => { - let err = Error::new(format!( - "BUG: Could not deserialize {} server JSON response properly, please \ - report this!\nReply from server: {}", - &conn.server_conf.server_url, &res_text - )) - .set_source(Some(Arc::new(err))) - .set_kind(ErrorKind::Bug); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); } Ok(s) => s, }; let m = ImportResponse::try_from(v.method_responses.remove(0)).map_err(|err| { - let ierr: Result = - serde_json::from_str(&res_text).map_err(|err| err.into()); + let ierr: Result = deserialize_from_str(&res_text); if let Ok(err) = ierr { Error::new(format!("Could not save message: {:?}", err)) } else { @@ -554,15 +555,8 @@ impl MailBackend for JmapType { .await?; let res_text = res.text().await?; - let mut v: MethodResponse = match serde_json::from_str(&res_text) { + let mut v: MethodResponse = match deserialize_from_str(&res_text) { Err(err) => { - let err = Error::new(format!( - "BUG: Could not deserialize {} server JSON response properly, please \ - report this!\nReply from server: {}", - &conn.server_conf.server_url, &res_text - )) - .set_source(Some(Arc::new(err))) - .set_kind(ErrorKind::Bug); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); } @@ -694,15 +688,8 @@ impl MailBackend for JmapType { let res_text = res.text().await?; - let mut v: MethodResponse = match serde_json::from_str(&res_text) { + let mut v: MethodResponse = match deserialize_from_str(&res_text) { Err(err) => { - let err = Error::new(format!( - "BUG: Could not deserialize {} server JSON response properly, please \ - report this!\nReply from server: {}", - &conn.server_conf.server_url, &res_text - )) - .set_source(Some(Arc::new(err))) - .set_kind(ErrorKind::Bug); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); } @@ -816,15 +803,8 @@ impl MailBackend for JmapType { * p-5;vfs-0"} */ //debug!("res_text = {}", &res_text); - let mut v: MethodResponse = match serde_json::from_str(&res_text) { + let mut v: MethodResponse = match deserialize_from_str(&res_text) { Err(err) => { - let err = Error::new(format!( - "BUG: Could not deserialize {} server JSON response properly, please \ - report this!\nReply from server: {}", - &conn.server_conf.server_url, &res_text - )) - .set_source(Some(Arc::new(err))) - .set_kind(ErrorKind::Bug); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); } diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs index ec026255..ffd8243d 100644 --- a/melib/src/backends/jmap/connection.rs +++ b/melib/src/backends/jmap/connection.rs @@ -100,8 +100,8 @@ impl JmapConnection { jmap_session_resource_url = to_well_known(&self.server_conf.server_url); if let Ok(s) = self.client.get_async(&jmap_session_resource_url).await { log::error!( - "Account {} server URL should start with `https`. Please correct \ - your configuration value. Its current value is `{}`.", + "Account {} server URL should start with `https`. Please correct your \ + configuration value. Its current value is `{}`.", self.store.account_name, self.server_conf.server_url ); @@ -151,7 +151,7 @@ impl JmapConnection { Ok(s) => s, }; - let session: JmapSession = match serde_json::from_str(&res_text) { + let session: JmapSession = match deserialize_from_str(&res_text) { Err(err) => { let err = Error::new(format!( "Could not connect to JMAP server endpoint for {}. Is your server url setting \ @@ -167,14 +167,36 @@ impl JmapConnection { Ok(s) => s, }; if !session.capabilities.contains_key(JMAP_CORE_CAPABILITY) { - let err = Error::new(format!("Server {} did not return JMAP Core capability ({core_capability}). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::>().join(", "), core_capability=JMAP_CORE_CAPABILITY)); + let err = Error::new(format!( + "Server {} did not return JMAP Core capability ({core_capability}). Returned \ + capabilities were: {}", + &self.server_conf.server_url, + session + .capabilities + .keys() + .map(String::as_str) + .collect::>() + .join(", "), + core_capability = JMAP_CORE_CAPABILITY + )); *self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); } *self.store.core_capabilities.lock().unwrap() = session.capabilities[JMAP_CORE_CAPABILITY].clone(); if !session.capabilities.contains_key(JMAP_MAIL_CAPABILITY) { - let err = Error::new(format!("Server {} does not support JMAP Mail capability ({mail_capability}). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::>().join(", "), mail_capability=JMAP_MAIL_CAPABILITY)); + let err = Error::new(format!( + "Server {} does not support JMAP Mail capability ({mail_capability}). Returned \ + capabilities were: {}", + &self.server_conf.server_url, + session + .capabilities + .keys() + .map(String::as_str) + .collect::>() + .join(", "), + mail_capability = JMAP_MAIL_CAPABILITY + )); *self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); } @@ -266,15 +288,8 @@ impl JmapConnection { let res_text = res.text().await?; debug!(&res_text); - let mut v: MethodResponse = match serde_json::from_str(&res_text) { + let mut v: MethodResponse = match deserialize_from_str(&res_text) { Err(err) => { - let err = Error::new(format!( - "BUG: Could not deserialize {} server JSON response properly, please \ - report this!\nReply from server: {}", - &self.server_conf.server_url, &res_text - )) - .set_source(Some(Arc::new(err))) - .set_kind(ErrorKind::Bug); *self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); } @@ -446,11 +461,10 @@ impl JmapConnection { let res_text = res.text().await?; debug!(&res_text); - let _: MethodResponse = match serde_json::from_str(&res_text) { + let _: MethodResponse = match deserialize_from_str(&res_text) { Err(err) => { - let err = Error::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &self.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug); - *self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); log::error!("{}", &err); + *self.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); } Ok(s) => s, diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index 96929749..c75c15a3 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -337,7 +337,7 @@ impl std::convert::From for crate::Envelope { pub struct HtmlBody { pub blob_id: Id, #[serde(default)] - pub charset: String, + pub charset: Option, #[serde(default)] pub cid: Option, #[serde(default)] @@ -364,7 +364,7 @@ pub struct HtmlBody { pub struct TextBody { pub blob_id: Id, #[serde(default)] - pub charset: String, + pub charset: Option, #[serde(default)] pub cid: Option, #[serde(default)] @@ -854,16 +854,9 @@ pub struct EmailQueryChangesResponse { impl std::convert::TryFrom<&RawValue> for EmailQueryChangesResponse { type Error = crate::error::Error; - fn try_from(t: &RawValue) -> Result { - let res: (String, Self, String) = serde_json::from_str(t.get()).map_err(|err| { - crate::error::Error::new(format!( - "BUG: Could not deserialize server JSON response properly, please report \ - this!\nReply from server: {}", - &t - )) - .set_source(Some(Arc::new(err))) - .set_kind(ErrorKind::Bug) - })?; + + fn try_from(t: &RawValue) -> std::result::Result { + let res: (String, Self, String) = deserialize_from_str(t.get())?; assert_eq!(&res.0, "Email/queryChanges"); Ok(res.1) } diff --git a/melib/src/backends/jmap/objects/email/import.rs b/melib/src/backends/jmap/objects/email/import.rs index 1406b4ea..926405a3 100644 --- a/melib/src/backends/jmap/objects/email/import.rs +++ b/melib/src/backends/jmap/objects/email/import.rs @@ -196,16 +196,9 @@ pub struct ImportResponse { impl std::convert::TryFrom<&RawValue> for ImportResponse { type Error = crate::error::Error; + fn try_from(t: &RawValue) -> Result { - let res: (String, Self, String) = serde_json::from_str(t.get()).map_err(|err| { - crate::error::Error::new(format!( - "BUG: Could not deserialize server JSON response properly, please report \ - this!\nReply from server: {}", - &t - )) - .set_source(Some(Arc::new(err))) - .set_kind(ErrorKind::Bug) - })?; + let res: (String, Self, String) = deserialize_from_str(t.get())?; assert_eq!(&res.0, &ImportCall::NAME); Ok(res.1) } diff --git a/melib/src/backends/jmap/objects/thread.rs b/melib/src/backends/jmap/objects/thread.rs index 40a526a4..5e431f97 100644 --- a/melib/src/backends/jmap/objects/thread.rs +++ b/melib/src/backends/jmap/objects/thread.rs @@ -19,9 +19,10 @@ * along with meli. If not, see . */ -use super::*; use core::marker::PhantomData; +use super::*; + #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ThreadObject { diff --git a/melib/src/backends/jmap/operations.rs b/melib/src/backends/jmap/operations.rs index 72074242..b59539b9 100644 --- a/melib/src/backends/jmap/operations.rs +++ b/melib/src/backends/jmap/operations.rs @@ -49,8 +49,7 @@ impl BackendOp for JmapOp { fn as_bytes(&mut self) -> ResultFuture> { { let byte_lck = self.store.byte_cache.lock().unwrap(); - if byte_lck.contains_key(&self.hash) && byte_lck[&self.hash].bytes.is_some() { - let ret = byte_lck[&self.hash].bytes.clone().unwrap(); + if let Some(Some(ret)) = byte_lck.get(&self.hash).map(|c| c.bytes.clone()) { return Ok(Box::pin(async move { Ok(ret.into_bytes()) })); } } diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 6ad21428..1b6fb430 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -90,7 +90,7 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result::try_from(v.method_responses.remove(0))?; let GetResponse:: { @@ -191,15 +191,8 @@ pub async fn get_message_list( .await?; let res_text = res.text().await?; - let mut v: MethodResponse = match serde_json::from_str(&res_text) { + let mut v: MethodResponse = match deserialize_from_str(&res_text) { Err(err) => { - let err = Error::new(format!( - "BUG: Could not deserialize {} server JSON response properly, please report \ - this!\nReply from server: {}", - &conn.server_conf.server_url, &res_text - )) - .set_source(Some(Arc::new(err))) - .set_kind(ErrorKind::Bug); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); } @@ -259,7 +252,7 @@ impl EmailFetchState { let current_state_lck = mbox.email_state.lock().unwrap(); ( current_state_lck.is_none(), - current_state_lck.as_ref() != Some(&state), + current_state_lck.as_ref() == Some(&state), ) }) .unwrap_or((true, true)) @@ -324,15 +317,8 @@ impl EmailFetchState { let _prev_seq = req.add_call(&email_call); let res_text = conn.send_request(serde_json::to_string(&req)?).await?; - let mut v: MethodResponse = match serde_json::from_str(&res_text) { + let mut v: MethodResponse = match deserialize_from_str(&res_text) { Err(err) => { - let err = Error::new(format!( - "BUG: Could not deserialize {} server JSON response properly, please report \ - this!\nReply from server: {}", - &conn.server_conf.server_url, &res_text - )) - .set_source(Some(Arc::new(err))) - .set_kind(ErrorKind::Bug); *conn.store.online_status.lock().await = (Instant::now(), Err(err.clone())); return Err(err); diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index b6542293..9c6a14f2 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -42,7 +42,7 @@ use std::collections::HashMap; pub use argument::*; -use super::protocol::Method; +use super::{deserialize_from_str, protocol::Method}; pub trait Object { const NAME: &'static str; } @@ -282,7 +282,6 @@ impl Object for BlobObject { /// - `account_id`: `Id` /// /// The id of the account to use. -/// #[derive(Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct Get @@ -427,16 +426,9 @@ pub struct GetResponse { impl std::convert::TryFrom<&RawValue> for GetResponse { type Error = crate::error::Error; + fn try_from(t: &RawValue) -> Result { - let res: (String, Self, String) = serde_json::from_str(t.get()).map_err(|err| { - crate::error::Error::new(format!( - "BUG: Could not deserialize server JSON response properly, please report \ - this!\nReply from server: {}", - &t - )) - .set_source(Some(Arc::new(err))) - .set_kind(crate::error::ErrorKind::Bug) - })?; + let res: (String, Self, String) = deserialize_from_str(t.get())?; assert_eq!(&res.0, &format!("{}/get", OBJ::NAME)); Ok(res.1) } @@ -548,16 +540,9 @@ pub struct QueryResponse { impl std::convert::TryFrom<&RawValue> for QueryResponse { type Error = crate::error::Error; - fn try_from(t: &RawValue) -> Result { - let res: (String, Self, String) = serde_json::from_str(t.get()).map_err(|err| { - crate::error::Error::new(format!( - "BUG: Could not deserialize server JSON response properly, please report \ - this!\nReply from server: {}", - &t - )) - .set_source(Some(Arc::new(err))) - .set_kind(crate::error::ErrorKind::Bug) - })?; + + fn try_from(t: &RawValue) -> std::result::Result { + let res: (String, Self, String) = deserialize_from_str(t.get())?; assert_eq!(&res.0, &format!("{}/query", OBJ::NAME)); Ok(res.1) } @@ -618,7 +603,6 @@ impl, OBJ: Object> ResultField { /// to return. If supplied by the client, the value MUST be a /// positive integer greater than 0. If a value outside of this range /// is given, the server MUST re -/// #[derive(Deserialize, Serialize, Clone, Debug)] #[serde(rename_all = "camelCase")] /* ch-ch-ch-ch-ch-Changes */ @@ -697,16 +681,9 @@ pub struct ChangesResponse { impl std::convert::TryFrom<&RawValue> for ChangesResponse { type Error = crate::error::Error; - fn try_from(t: &RawValue) -> Result { - let res: (String, Self, String) = serde_json::from_str(t.get()).map_err(|err| { - crate::error::Error::new(format!( - "BUG: Could not deserialize server JSON response properly, please report \ - this!\nReply from server: {}", - &t - )) - .set_source(Some(Arc::new(err))) - .set_kind(crate::error::ErrorKind::Bug) - })?; + + fn try_from(t: &RawValue) -> std::result::Result { + let res: (String, Self, String) = deserialize_from_str(t.get())?; assert_eq!(&res.0, &format!("{}/changes", OBJ::NAME)); Ok(res.1) } @@ -911,16 +888,9 @@ pub struct SetResponse { impl std::convert::TryFrom<&RawValue> for SetResponse { type Error = crate::error::Error; + fn try_from(t: &RawValue) -> Result { - let res: (String, Self, String) = serde_json::from_str(t.get()).map_err(|err| { - crate::error::Error::new(format!( - "BUG: Could not deserialize server JSON response properly, please report \ - this!\nReply from server: {}", - &t - )) - .set_source(Some(Arc::new(err))) - .set_kind(crate::error::ErrorKind::Bug) - })?; + let res: (String, Self, String) = deserialize_from_str(t.get())?; assert_eq!(&res.0, &format!("{}/set", OBJ::NAME)); Ok(res.1) } @@ -1038,6 +1008,16 @@ pub fn download_request_format( } else if download_url[prev_pos..].starts_with("{name}") { ret.push_str(name.as_deref().unwrap_or("")); prev_pos += "{name}".len(); + } else if download_url[prev_pos..].starts_with("{type}") { + ret.push_str("application/octet-stream"); + prev_pos += "{name}".len(); + } else { + // [ref:FIXME]: return protocol error here + log::error!( + "BUG: unknown parameter in download url: {}", + &download_url[prev_pos..] + ); + break; } } if prev_pos != download_url.len() {