Skip to main content

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}