jmap: request the Thread property the spec names
Thread/get sorts and reads emailIds (RFC 8621 §3), not emails —
the fixtures shared the invented name, so nothing contradicted it
until Cyrus did.
Assisted-by: GLM-5.3 via pi
diff --git a/src/jmap.rs b/src/jmap.rs
index f7860b8..95503f0 100644
--- a/src/jmap.rs
+++ b/src/jmap.rs
@@ -177,7 +177,7 @@ struct Query {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct ThreadEmails {
- emails: Vec<String>,
+ email_ids: Vec<String>,
}
/// Pulls one page of ids out of an `Email/query` response.
@@ -678,10 +678,10 @@ struct GetThreads<'a> {
#[serde(rename_all = "camelCase")]
enum ThreadProperty {
Id,
- Emails,
+ EmailIds,
}
-const THREAD_PROPERTIES: &[ThreadProperty] = &[ThreadProperty::Id, ThreadProperty::Emails];
+const THREAD_PROPERTIES: &[ThreadProperty] = &[ThreadProperty::Id, ThreadProperty::EmailIds];
/// `Foo/changes` arguments (RFC 8620 §5.2).
#[derive(Debug, Serialize)]
@@ -1002,7 +1002,12 @@ impl Client {
)
.await?;
for thread in parse_list::<ThreadEmails>("Thread/get", args)? {
- wanted.extend(thread.emails.into_iter().filter(|id| !known.contains(id)));
+ wanted.extend(
+ thread
+ .email_ids
+ .into_iter()
+ .filter(|id| !known.contains(id)),
+ );
}
}
for chunk in wanted.chunks(PAGE) {
@@ -1334,7 +1339,7 @@ mod tests {
.unwrap();
assert_eq!(
threads,
- json!({"accountId": "a", "ids": ["t1"], "properties": ["id", "emails"]})
+ json!({"accountId": "a", "ids": ["t1"], "properties": ["id", "emailIds"]})
);
let changes = serde_json::to_value(GetChanges {
diff --git a/tests/jmap.rs b/tests/jmap.rs
index 78d1dee..52e5ba4 100644
--- a/tests/jmap.rs
+++ b/tests/jmap.rs
@@ -292,11 +292,11 @@ fn emails_of(account: &str) -> Vec<Value> {
fn threads_of(account: &str) -> Vec<Value> {
match account {
HOUSEHOLD => vec![
- json!({"id": "T-roofer", "emails": ["E-roofer1", "E-roofer2", "E-reply1"]}),
- json!({"id": "T-school", "emails": ["E-school", "E-reply2", "E-draft"]}),
- json!({"id": "T-ancient", "emails": ["E-reply3"]}),
+ json!({"id": "T-roofer", "emailIds": ["E-roofer1", "E-roofer2", "E-reply1"]}),
+ json!({"id": "T-school", "emailIds": ["E-school", "E-reply2", "E-draft"]}),
+ json!({"id": "T-ancient", "emailIds": ["E-reply3"]}),
],
- ELI => vec![json!({"id": "T-practice", "emails": ["E-practice"]})],
+ ELI => vec![json!({"id": "T-practice", "emailIds": ["E-practice"]})],
_ => Vec::new(),
}
}
@@ -540,6 +540,16 @@ async fn api(world: axum::extract::State<World>, headers: HeaderMap, body: Strin
.collect::<Vec<_>>()})
}
"Thread/get" => {
+ // A Thread's only properties are id and emailIds (RFC 8621
+ // §3); servers answer anything else with invalidArguments.
+ if let Some(properties) = args["properties"].as_array() {
+ for property in properties {
+ assert!(
+ matches!(property.as_str(), Some("id") | Some("emailIds")),
+ "unknown Thread property {property}"
+ );
+ }
+ }
let wanted: Vec<&str> = args["ids"]
.as_array()
.map(|ids| ids.iter().filter_map(Value::as_str).collect())