io-email 0.1.0

Email client library
Documentation
//! Mailbox shared across all protocols.

use alloc::{string::String, vec::Vec};

/// A mailbox (a.k.a. folder).
///
/// Strict least-common-denominator shape: only fields that are
/// first-class in every protocol the crate targets (IMAP, JMAP,
/// Maildir, m2dir, mbox, notmuch). Protocol-specific data (IMAP
/// delimiter and SPECIAL-USE attributes, JMAP role and rights,
/// Maildir path, …) is intentionally absent — for these, use the
/// corresponding protocol-specific crate directly.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub struct Mailbox {
    /// Backend-specific identifier.
    ///
    /// JMAP exposes a real opaque ID; for IMAP, Maildir, mbox and
    /// notmuch this is the same as [`Self::name`]. Use this when
    /// issuing follow-up commands that refer to the mailbox.
    pub id: String,

    /// Human-readable mailbox name.
    pub name: String,

    /// Total number of messages, when the caller requested counts.
    /// `None` when the backend was not asked or cannot answer
    /// cheaply.
    #[cfg_attr(feature = "serde", serde(default))]
    pub total: Option<u64>,

    /// Number of unread messages, when the caller requested counts.
    /// `None` when the backend was not asked or cannot answer
    /// cheaply.
    #[cfg_attr(feature = "serde", serde(default))]
    pub unread: Option<u64>,
}

/// Special-use role of a mailbox.
///
/// Mirrors the IANA JMAP mailbox roles and the IMAP SPECIAL-USE
/// attributes (RFC 6154). [`MailboxRole::Other`] holds any value that
/// does not match a known role.
///
/// Not part of the shared [`Mailbox`] shape — only IMAP and JMAP
/// expose roles natively. Protocol-specific commands consume this
/// enum directly when they need to render or filter by role.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum MailboxRole {
    Inbox,
    Archive,
    Drafts,
    Flagged,
    Important,
    Junk,
    Sent,
    Trash,
    Other(String),
}

impl MailboxRole {
    pub fn parse(raw: &str) -> Self {
        match raw.trim_start_matches('\\').to_ascii_lowercase().as_str() {
            "inbox" => Self::Inbox,
            "archive" => Self::Archive,
            "drafts" => Self::Drafts,
            "flagged" => Self::Flagged,
            "important" => Self::Important,
            "junk" | "spam" => Self::Junk,
            "sent" => Self::Sent,
            "trash" => Self::Trash,
            _ => Self::Other(raw.into()),
        }
    }
}

/// Outcome of an incremental mailbox-diff call.
///
/// `new_state` is the opaque per-backend mailbox-set checkpoint to
/// persist for the next call; format is private to the backend impl
/// (JMAP stores the raw `Mailbox/state` bytes). Backends without an
/// account-global mailbox state token (IMAP, m2dir, maildir) return
/// `Err(EmailClientStdError::UnsupportedOperation)`; callers fall back
/// to a normal mailbox listing in that case.
#[derive(Clone, Debug)]
pub enum MailboxDiff {
    /// Mailbox set is identical to the cached checkpoint. Caller may
    /// reuse its prior mailbox list and skip the listing round-trip.
    Unchanged { new_state: Vec<u8> },

    /// Mailbox set may have changed, or no checkpoint was cached.
    /// Caller must list mailboxes; `new_state` is `None` when the
    /// backend could not cheaply capture a baseline without listing.
    Changed { new_state: Option<Vec<u8>> },
}