diff --git a/CHANGELOG.md b/CHANGELOG.md index 69f09f2f..3c3dafb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If - Unchanged immutable `id` property is rejected on `/set`. - `filter: null` rejected as `notRequest` on `/query` and `/queryChanges`. - `Email/query` total miscount when `collapseThreads` is enabled. + - `SearchSnippet/get` response structure. - OIDC: Add default domain name to groups that are not email addresses. - RocksDB: Enable blob garbage collection to reclaim disk space from deleted blobs. diff --git a/crates/jmap-proto/src/method/search_snippet.rs b/crates/jmap-proto/src/method/search_snippet.rs index e6081c9e..70ddbe7a 100644 --- a/crates/jmap-proto/src/method/search_snippet.rs +++ b/crates/jmap-proto/src/method/search_snippet.rs @@ -33,8 +33,7 @@ pub struct GetSearchSnippetResponse { pub list: Vec, #[serde(rename = "notFound")] - #[serde(skip_serializing_if = "Vec::is_empty")] - pub not_found: Vec, + pub not_found: Option>>, } #[derive(serde::Serialize, Clone, Debug)] @@ -42,10 +41,8 @@ pub struct SearchSnippet { #[serde(rename = "emailId")] pub email_id: Id, - #[serde(skip_serializing_if = "Option::is_none")] pub subject: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub preview: Option, } diff --git a/crates/jmap-proto/src/request/mod.rs b/crates/jmap-proto/src/request/mod.rs index d2db6915..3b25284a 100644 --- a/crates/jmap-proto/src/request/mod.rs +++ b/crates/jmap-proto/src/request/mod.rs @@ -189,6 +189,18 @@ impl<'de, V: FromStr> serde::Deserialize<'de> for MaybeInvalid { } } +impl serde::Serialize for MaybeInvalid { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + MaybeInvalid::Value(v) => v.serialize(serializer), + MaybeInvalid::Invalid(s) => serializer.serialize_str(s), + } + } +} + impl Default for MaybeInvalid { fn default() -> Self { MaybeInvalid::Invalid("".to_string()) diff --git a/crates/jmap/src/email/snippet.rs b/crates/jmap/src/email/snippet.rs index 3628653a..0127abe7 100644 --- a/crates/jmap/src/email/snippet.rs +++ b/crates/jmap/src/email/snippet.rs @@ -17,7 +17,7 @@ use jmap_proto::{ search_snippet::{GetSearchSnippetRequest, GetSearchSnippetResponse, SearchSnippet}, }, object::email::EmailFilter, - request::IntoValid, + request::MaybeInvalid, }; use mail_parser::decoders::html::html_to_text; use nlp::language::{Language, search_snippet::generate_snippet, stemmer::Stemmer}; @@ -108,14 +108,22 @@ impl EmailSearchSnippet for Server { let mut response = GetSearchSnippetResponse { account_id: request.account_id, list: Vec::with_capacity(email_ids.len()), - not_found: vec![], + not_found: None, }; + let mut not_found = Vec::new(); if email_ids.len() > self.core.jmap.snippet_max_results { return Err(trc::JmapEvent::RequestTooLarge.into_err()); } - for email_id in email_ids.into_valid() { + for email_id in email_ids { + let email_id = match email_id { + MaybeInvalid::Value(email_id) => email_id, + invalid => { + not_found.push(invalid); + continue; + } + }; let document_id = email_id.document_id(); let mut snippet = SearchSnippet { email_id, @@ -123,7 +131,7 @@ impl EmailSearchSnippet for Server { preview: None, }; if !document_ids.contains(document_id) { - response.not_found.push(email_id); + not_found.push(MaybeInvalid::Value(email_id)); continue; } else if terms.is_empty() { response.list.push(snippet); @@ -141,7 +149,7 @@ impl EmailSearchSnippet for Server { { Some(metadata) => metadata, None => { - response.not_found.push(email_id); + not_found.push(MaybeInvalid::Value(email_id)); continue; } }; @@ -178,7 +186,7 @@ impl EmailSearchSnippet for Server { CausedBy = trc::location!(), ); - response.not_found.push(email_id); + not_found.push(MaybeInvalid::Value(email_id)); continue; }; let raw_message = ChainedBytes::new(metadata.raw_headers.as_ref()).with_last( @@ -246,6 +254,10 @@ impl EmailSearchSnippet for Server { response.list.push(snippet); } + if !not_found.is_empty() { + response.not_found = Some(not_found); + } + Ok(response) } }