io_email/mailbox/types.rs
1//! Mailbox shared across all protocols.
2
3use alloc::{string::String, vec::Vec};
4
5/// A mailbox (a.k.a. folder).
6///
7/// Strict least-common-denominator shape: only fields that are
8/// first-class in every protocol the crate targets (IMAP, JMAP,
9/// Maildir, m2dir, mbox, notmuch). Protocol-specific data (IMAP
10/// delimiter and SPECIAL-USE attributes, JMAP role and rights,
11/// Maildir path, …) is intentionally absent — for these, use the
12/// corresponding protocol-specific crate directly.
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
16pub struct Mailbox {
17 /// Backend-specific identifier.
18 ///
19 /// JMAP exposes a real opaque ID; for IMAP, Maildir, mbox and
20 /// notmuch this is the same as [`Self::name`]. Use this when
21 /// issuing follow-up commands that refer to the mailbox.
22 pub id: String,
23
24 /// Human-readable mailbox name.
25 pub name: String,
26
27 /// Total number of messages, when the caller requested counts.
28 /// `None` when the backend was not asked or cannot answer
29 /// cheaply.
30 #[cfg_attr(feature = "serde", serde(default))]
31 pub total: Option<u64>,
32
33 /// Number of unread messages, when the caller requested counts.
34 /// `None` when the backend was not asked or cannot answer
35 /// cheaply.
36 #[cfg_attr(feature = "serde", serde(default))]
37 pub unread: Option<u64>,
38}
39
40/// Special-use role of a mailbox.
41///
42/// Mirrors the IANA JMAP mailbox roles and the IMAP SPECIAL-USE
43/// attributes (RFC 6154). [`MailboxRole::Other`] holds any value that
44/// does not match a known role.
45///
46/// Not part of the shared [`Mailbox`] shape — only IMAP and JMAP
47/// expose roles natively. Protocol-specific commands consume this
48/// enum directly when they need to render or filter by role.
49#[derive(Clone, Debug, PartialEq, Eq, Hash)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
52pub enum MailboxRole {
53 Inbox,
54 Archive,
55 Drafts,
56 Flagged,
57 Important,
58 Junk,
59 Sent,
60 Trash,
61 Other(String),
62}
63
64impl MailboxRole {
65 pub fn parse(raw: &str) -> Self {
66 match raw.trim_start_matches('\\').to_ascii_lowercase().as_str() {
67 "inbox" => Self::Inbox,
68 "archive" => Self::Archive,
69 "drafts" => Self::Drafts,
70 "flagged" => Self::Flagged,
71 "important" => Self::Important,
72 "junk" | "spam" => Self::Junk,
73 "sent" => Self::Sent,
74 "trash" => Self::Trash,
75 _ => Self::Other(raw.into()),
76 }
77 }
78}
79
80/// Outcome of an incremental mailbox-diff call.
81///
82/// `new_state` is the opaque per-backend mailbox-set checkpoint to
83/// persist for the next call; format is private to the backend impl
84/// (JMAP stores the raw `Mailbox/state` bytes). Backends without an
85/// account-global mailbox state token (IMAP, m2dir, maildir) return
86/// `Err(EmailClientStdError::UnsupportedOperation)`; callers fall back
87/// to a normal mailbox listing in that case.
88#[derive(Clone, Debug)]
89pub enum MailboxDiff {
90 /// Mailbox set is identical to the cached checkpoint. Caller may
91 /// reuse its prior mailbox list and skip the listing round-trip.
92 Unchanged { new_state: Vec<u8> },
93
94 /// Mailbox set may have changed, or no checkpoint was cached.
95 /// Caller must list mailboxes; `new_state` is `None` when the
96 /// backend could not cheaply capture a baseline without listing.
97 Changed { new_state: Option<Vec<u8>> },
98}