1use jmap_types::{GetObject, JmapObject, PatchObject, QueryObject, SetObject};
8
9#[non_exhaustive]
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum MailboxProperty {
17 Id,
19 Name,
21 ParentId,
23 Role,
25 SortOrder,
27 TotalEmails,
29 UnreadEmails,
31 TotalThreads,
33 UnreadThreads,
35 MyRights,
37 IsSubscribed,
39}
40
41#[non_exhaustive]
43#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub enum ThreadProperty {
45 Id,
47 EmailIds,
49}
50
51#[non_exhaustive]
53#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54pub enum EmailProperty {
55 Id,
57 BlobId,
59 ThreadId,
61 MailboxIds,
63 Keywords,
65 Size,
67 ReceivedAt,
69 MessageId,
71 InReplyTo,
73 References,
75 Subject,
77 From,
79 To,
81 Cc,
83 Bcc,
85 ReplyTo,
87 Sender,
89 SentAt,
91 HasAttachment,
93 Preview,
95 BodyStructure,
97 TextBody,
99 HtmlBody,
101 Attachments,
103 BodyValues,
105 Headers,
107}
108
109#[non_exhaustive]
111#[derive(Debug, Clone, PartialEq, Eq, Hash)]
112pub enum IdentityProperty {
113 Id,
115 Name,
117 Email,
119 ReplyTo,
121 Bcc,
123 TextSignature,
125 HtmlSignature,
127 MayDelete,
129}
130
131#[non_exhaustive]
133#[derive(Debug, Clone, PartialEq, Eq, Hash)]
134pub enum EmailSubmissionProperty {
135 Id,
137 IdentityId,
139 EmailId,
141 ThreadId,
143 Envelope,
145 SendAt,
147 UndoStatus,
149 DeliveryStatus,
151 DsnBlobIds,
153 MdnBlobIds,
155}
156
157#[non_exhaustive]
159#[derive(Debug, Clone, PartialEq, Eq, Hash)]
160pub enum VacationResponseProperty {
161 Id,
163 IsEnabled,
165 FromDate,
167 ToDate,
169 Subject,
171 TextBody,
173 HtmlBody,
175}
176
177#[non_exhaustive]
179#[derive(Debug, Clone, PartialEq, Eq, Hash)]
180pub enum SearchSnippetProperty {
181 EmailId,
183 Subject,
185 Preview,
187}
188
189#[cfg(feature = "sieve")]
191#[non_exhaustive]
192#[derive(Debug, Clone, PartialEq, Eq, Hash)]
193pub enum SieveScriptProperty {
194 Id,
196 Name,
198 BlobId,
200 IsActive,
202}
203
204impl JmapObject for crate::Mailbox {
209 const TYPE_NAME: &'static str = "Mailbox";
210 type Property = MailboxProperty;
211}
212
213impl GetObject for crate::Mailbox {}
214
215impl SetObject for crate::Mailbox {
220 type Patch = PatchObject;
221 const SERVER_SET: &'static [&'static str] = &[
222 "id",
223 "totalEmails",
224 "unreadEmails",
225 "totalThreads",
226 "unreadThreads",
227 "myRights",
228 ];
229 const CLIENT_SETTABLE: &'static [&'static str] = &[
230 "name",
231 "parentId",
232 "role",
233 "sortOrder",
234 "isSubscribed",
235 "metadata",
236 "privateMetadata",
237 ];
238 const IMMUTABLE: &'static [&'static str] = &[];
239}
240
241impl QueryObject for crate::Mailbox {
242 type Filter = crate::MailboxFilter;
243 type Comparator = crate::MailboxComparator;
244}
245
246impl JmapObject for crate::Thread {
247 const TYPE_NAME: &'static str = "Thread";
248 type Property = ThreadProperty;
249}
250
251impl GetObject for crate::Thread {}
252
253impl JmapObject for crate::Email {
254 const TYPE_NAME: &'static str = "Email";
255 type Property = EmailProperty;
256}
257
258impl GetObject for crate::Email {}
259
260impl SetObject for crate::Email {
267 type Patch = PatchObject;
268 const SERVER_SET: &'static [&'static str] = &[
269 "id",
270 "blobId",
271 "threadId",
272 "size",
273 "hasAttachment",
274 "preview",
275 ];
276 const CLIENT_SETTABLE: &'static [&'static str] = &[
277 "mailboxIds",
278 "keywords",
279 "receivedAt",
280 "messageId",
281 "inReplyTo",
282 "references",
283 "sender",
284 "from",
285 "to",
286 "cc",
287 "bcc",
288 "replyTo",
289 "subject",
290 "sentAt",
291 "headers",
292 "bodyStructure",
293 "bodyValues",
294 "textBody",
295 "htmlBody",
296 "attachments",
297 "metadata",
298 "privateMetadata",
299 ];
300 const IMMUTABLE: &'static [&'static str] = &[
301 "receivedAt",
302 "messageId",
303 "inReplyTo",
304 "references",
305 "sender",
306 "from",
307 "to",
308 "cc",
309 "bcc",
310 "replyTo",
311 "subject",
312 "sentAt",
313 "headers",
314 "bodyStructure",
315 "bodyValues",
316 "textBody",
317 "htmlBody",
318 "attachments",
319 ];
320}
321
322impl QueryObject for crate::Email {
323 type Filter = crate::EmailFilter;
324 type Comparator = crate::EmailComparator;
325}
326
327impl JmapObject for crate::Identity {
328 const TYPE_NAME: &'static str = "Identity";
329 type Property = IdentityProperty;
330}
331
332impl GetObject for crate::Identity {}
333
334impl SetObject for crate::Identity {
337 type Patch = PatchObject;
338 const SERVER_SET: &'static [&'static str] = &["id", "mayDelete"];
339 const CLIENT_SETTABLE: &'static [&'static str] = &[
340 "name",
341 "email",
342 "replyTo",
343 "bcc",
344 "textSignature",
345 "htmlSignature",
346 ];
347 const IMMUTABLE: &'static [&'static str] = &["email"];
348}
349
350impl JmapObject for crate::EmailSubmission {
351 const TYPE_NAME: &'static str = "EmailSubmission";
352 type Property = EmailSubmissionProperty;
353}
354
355impl GetObject for crate::EmailSubmission {}
356
357impl SetObject for crate::EmailSubmission {
362 type Patch = PatchObject;
363 const SERVER_SET: &'static [&'static str] = &[
364 "id",
365 "threadId",
366 "sendAt",
367 "deliveryStatus",
368 "dsnBlobIds",
369 "mdnBlobIds",
370 ];
371 const CLIENT_SETTABLE: &'static [&'static str] =
372 &["identityId", "emailId", "envelope", "undoStatus"];
373 const IMMUTABLE: &'static [&'static str] = &["identityId", "emailId", "envelope"];
374}
375
376impl QueryObject for crate::EmailSubmission {
377 type Filter = crate::EmailSubmissionFilter;
378 type Comparator = crate::EmailSubmissionComparator;
379}
380
381impl JmapObject for crate::VacationResponse {
382 const TYPE_NAME: &'static str = "VacationResponse";
383 type Property = VacationResponseProperty;
384}
385
386impl GetObject for crate::VacationResponse {}
387
388impl SetObject for crate::VacationResponse {
391 type Patch = PatchObject;
392 const SERVER_SET: &'static [&'static str] = &["id"];
393 const CLIENT_SETTABLE: &'static [&'static str] = &[
394 "isEnabled",
395 "fromDate",
396 "toDate",
397 "subject",
398 "textBody",
399 "htmlBody",
400 ];
401 const IMMUTABLE: &'static [&'static str] = &[];
402}
403
404impl JmapObject for crate::SearchSnippet {
405 const TYPE_NAME: &'static str = "SearchSnippet";
406 type Property = SearchSnippetProperty;
407}
408
409#[cfg(feature = "sieve")]
410impl JmapObject for crate::sieve::SieveScript {
411 const TYPE_NAME: &'static str = "SieveScript";
412 type Property = SieveScriptProperty;
413}
414
415#[cfg(feature = "sieve")]
416impl GetObject for crate::sieve::SieveScript {}
417
418#[cfg(feature = "sieve")]
421impl SetObject for crate::sieve::SieveScript {
422 type Patch = PatchObject;
423 const SERVER_SET: &'static [&'static str] = &["id", "isActive"];
424 const CLIENT_SETTABLE: &'static [&'static str] = &["name", "blobId"];
425 const IMMUTABLE: &'static [&'static str] = &[];
426}
427
428#[cfg(test)]
429mod tests {
430 use jmap_types::{Date, Id, UTCDate, check_property_classification};
435 use serde_json::{Map, Value, json};
436 use std::collections::BTreeMap;
437
438 fn de<T: serde::de::DeserializeOwned>(v: Value) -> T {
439 serde_json::from_value(v).expect("hand-written fixture deserializes")
440 }
441
442 fn addrs() -> Option<Vec<crate::EmailAddress>> {
443 Some(de(json!([{"name": "A", "email": "a@example.com"}])))
444 }
445
446 fn part() -> crate::EmailBodyPart {
447 de(json!({"partId": "1", "type": "text/plain"}))
448 }
449
450 #[test]
451 fn mailbox_properties_are_classified() {
452 let obj = crate::Mailbox {
453 id: Id::from("m1"),
454 name: "Inbox".into(),
455 parent_id: Some(Id::from("m0")),
456 role: Some(de(json!("inbox"))),
457 sort_order: 1,
458 total_emails: 1,
459 unread_emails: 1,
460 total_threads: 1,
461 unread_threads: 1,
462 my_rights: crate::MailboxRights::default(),
463 is_subscribed: true,
464 metadata: Some(de(json!({"acme.example.com": {"memo": "m"}}))),
465 private_metadata: Some(de(json!({"acme.example.com": {"memo": "p"}}))),
466 extra: Map::new(),
467 };
468 assert_eq!(check_property_classification(&obj), Ok(()));
469 }
470
471 #[test]
472 fn email_properties_are_classified() {
473 let obj = crate::Email {
474 id: Id::from("e1"),
475 blob_id: Id::from("b1"),
476 thread_id: Id::from("t1"),
477 mailbox_ids: BTreeMap::from([(Id::from("m1"), true)]),
478 keywords: de(json!({"$seen": true})),
479 size: 1,
480 received_at: UTCDate::from("2026-01-01T00:00:00Z"),
481 message_id: Some(vec!["a@example.com".into()]),
482 in_reply_to: Some(vec!["b@example.com".into()]),
483 references: Some(vec!["b@example.com".into()]),
484 sender: addrs(),
485 from: addrs(),
486 to: addrs(),
487 cc: addrs(),
488 bcc: addrs(),
489 reply_to: addrs(),
490 subject: Some("s".into()),
491 sent_at: Some(Date::from("2026-01-01T00:00:00Z")),
492 headers: de(json!([{"name": "Subject", "value": " s"}])),
493 body_values: de(json!({"1": {"value": "v"}})),
494 text_body: vec![part()],
495 html_body: vec![part()],
496 attachments: vec![part()],
497 body_structure: Some(part()),
498 has_attachment: true,
499 preview: Some("p".into()),
500 metadata: Some(de(json!({"acme.example.com": {"memo": "m"}}))),
501 private_metadata: Some(de(json!({"acme.example.com": {"memo": "p"}}))),
502 extra: Map::new(),
503 };
504 assert_eq!(check_property_classification(&obj), Ok(()));
505 }
506
507 #[test]
508 fn identity_properties_are_classified() {
509 let obj = crate::Identity {
510 id: Id::from("i1"),
511 name: "A".into(),
512 email: "a@example.com".into(),
513 reply_to: addrs(),
514 bcc: addrs(),
515 text_signature: "t".into(),
516 html_signature: "h".into(),
517 may_delete: true,
518 extra: Map::new(),
519 };
520 assert_eq!(check_property_classification(&obj), Ok(()));
521 }
522
523 #[test]
524 fn email_submission_properties_are_classified() {
525 let obj = crate::EmailSubmission {
526 id: Id::from("s1"),
527 identity_id: Id::from("i1"),
528 email_id: Id::from("e1"),
529 thread_id: Id::from("t1"),
530 envelope: Some(de(json!({
531 "mailFrom": {"email": "a@example.com"},
532 "rcptTo": [{"email": "b@example.com"}],
533 }))),
534 send_at: UTCDate::from("2026-01-01T00:00:00Z"),
535 undo_status: de(json!("pending")),
536 delivery_status: Some(de(json!({"b@example.com": {
537 "smtpReply": "250 OK",
538 "delivered": "queued",
539 "displayed": "unknown",
540 }}))),
541 dsn_blob_ids: vec![Id::from("d1")],
542 mdn_blob_ids: vec![Id::from("n1")],
543 extra: Map::new(),
544 };
545 assert_eq!(check_property_classification(&obj), Ok(()));
546 }
547
548 #[test]
549 fn vacation_response_properties_are_classified() {
550 let obj = crate::VacationResponse {
551 id: Id::from("singleton"),
552 is_enabled: true,
553 from_date: Some(UTCDate::from("2026-01-01T00:00:00Z")),
554 to_date: Some(UTCDate::from("2026-02-01T00:00:00Z")),
555 subject: Some("s".into()),
556 text_body: Some("t".into()),
557 html_body: Some("h".into()),
558 extra: Map::new(),
559 };
560 assert_eq!(check_property_classification(&obj), Ok(()));
561 }
562
563 #[cfg(feature = "sieve")]
564 #[test]
565 fn sieve_script_properties_are_classified() {
566 let obj = crate::SieveScript {
567 id: Id::from("s1"),
568 name: Some("n".into()),
569 blob_id: Id::from("b1"),
570 is_active: true,
571 extra: Map::new(),
572 };
573 assert_eq!(check_property_classification(&obj), Ok(()));
574 }
575}