use jmap_types::Id;
use serde::{Deserialize, Serialize};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchSnippet {
pub email_id: Id,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub preview: Option<String>,
#[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
pub extra: serde_json::Map<String, serde_json::Value>,
}
impl SearchSnippet {
pub fn new(email_id: Id) -> Self {
Self {
email_id,
subject: None,
preview: None,
extra: serde_json::Map::new(),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchSnippetGetResponse {
pub account_id: Id,
pub list: Vec<SearchSnippet>,
pub not_found: Option<Vec<Id>>,
#[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn search_snippet_preserves_vendor_extras() {
let raw = json!({
"emailId": "e1",
"subject": "Hello <mark>world</mark>",
"preview": "...the <mark>world</mark>...",
"acmeCorpRelevanceScore": 0.87
});
let snip: SearchSnippet = serde_json::from_value(raw).unwrap();
assert_eq!(
snip.extra
.get("acmeCorpRelevanceScore")
.and_then(|v| v.as_f64()),
Some(0.87)
);
let back = serde_json::to_value(&snip).unwrap();
assert_eq!(back["acmeCorpRelevanceScore"], 0.87);
}
#[test]
fn search_snippet_get_response_parses_rfc8621_example() {
let raw = json!({
"accountId": "ue150411c",
"list": [{
"emailId": "M44200ec123de277c0c1ce69c",
"subject": null,
"preview": null
}, {
"emailId": "M7bcbcb0b58d7729686e83d99",
"subject": "The <mark>Foo</mark>sball competition",
"preview": "...year the <mark>foo</mark>sball competition will be held in the Stadium de ..."
}],
"notFound": null
});
let resp: SearchSnippetGetResponse = serde_json::from_value(raw).unwrap();
assert_eq!(resp.account_id.as_ref(), "ue150411c");
assert_eq!(resp.list.len(), 2);
assert_eq!(resp.list[0].subject, None);
assert_eq!(
resp.list[1].subject.as_deref(),
Some("The <mark>Foo</mark>sball competition")
);
assert_eq!(resp.not_found, None);
assert!(resp.extra.is_empty());
}
#[test]
fn search_snippet_get_response_not_found_and_extras() {
let raw = json!({
"accountId": "A1",
"list": [],
"notFound": ["M9"],
"filter": { "text": "foo" }
});
let resp: SearchSnippetGetResponse = serde_json::from_value(raw.clone()).unwrap();
assert_eq!(resp.not_found, Some(vec![Id::from("M9")]));
assert_eq!(resp.extra["filter"], json!({ "text": "foo" }));
assert_eq!(serde_json::to_value(&resp).unwrap(), raw);
}
}