use super::fetch::FetchResponse;
use super::flag::Flag;
use super::response::UidRange;
use super::validated::MailboxName;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MailboxInfo {
pub name: MailboxName,
pub delimiter: Option<char>,
pub attributes: Vec<MailboxAttribute>,
pub old_name: Option<MailboxName>,
pub child_info: Vec<String>,
}
#[non_exhaustive]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum MailboxAttribute {
NoInferiors,
NoSelect,
NonExistent,
HasChildren,
HasNoChildren,
Marked,
Unmarked,
Subscribed,
Remote,
All,
Archive,
Drafts,
Flagged,
Junk,
Sent,
Trash,
Important,
NoAccess,
Memos,
Scheduled,
Snoozed,
Custom(String),
}
impl MailboxAttribute {
pub fn as_imap_str(&self) -> &str {
match self {
Self::NoInferiors => "\\Noinferiors",
Self::NoSelect => "\\Noselect",
Self::NonExistent => "\\NonExistent",
Self::HasChildren => "\\HasChildren",
Self::HasNoChildren => "\\HasNoChildren",
Self::Marked => "\\Marked",
Self::Unmarked => "\\Unmarked",
Self::Subscribed => "\\Subscribed",
Self::Remote => "\\Remote",
Self::All => "\\All",
Self::Archive => "\\Archive",
Self::Drafts => "\\Drafts",
Self::Flagged => "\\Flagged",
Self::Junk => "\\Junk",
Self::Sent => "\\Sent",
Self::Trash => "\\Trash",
Self::Important => "\\Important",
Self::NoAccess => "\\NoAccess",
Self::Memos => "\\Memos",
Self::Scheduled => "\\Scheduled",
Self::Snoozed => "\\Snoozed",
Self::Custom(s) => s,
}
}
pub fn is_special_use(&self) -> bool {
match self {
Self::All
| Self::Archive
| Self::Drafts
| Self::Flagged
| Self::Junk
| Self::Sent
| Self::Trash
| Self::Important
| Self::Memos
| Self::Scheduled
| Self::Snoozed => true,
Self::Custom(s) => {
Self::is_valid_special_use_attr_ext(s) && !Self::is_known_base_list_attr(s)
}
_ => false,
}
}
fn is_valid_special_use_attr_ext(s: &str) -> bool {
let Some(atom) = s.strip_prefix('\\') else {
return false;
};
crate::types::validated::validate_atom_bytes(atom.as_bytes(), "special-use attribute")
.is_ok()
}
fn is_known_base_list_attr(s: &str) -> bool {
s.eq_ignore_ascii_case("\\Noinferiors")
|| s.eq_ignore_ascii_case("\\Noselect")
|| s.eq_ignore_ascii_case("\\NonExistent")
|| s.eq_ignore_ascii_case("\\HasChildren")
|| s.eq_ignore_ascii_case("\\HasNoChildren")
|| s.eq_ignore_ascii_case("\\Marked")
|| s.eq_ignore_ascii_case("\\Unmarked")
|| s.eq_ignore_ascii_case("\\Subscribed")
|| s.eq_ignore_ascii_case("\\Remote")
|| s.eq_ignore_ascii_case("\\NoAccess")
}
}
impl PartialEq for MailboxAttribute {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::NoInferiors, Self::NoInferiors)
| (Self::NoSelect, Self::NoSelect)
| (Self::NonExistent, Self::NonExistent)
| (Self::HasChildren, Self::HasChildren)
| (Self::HasNoChildren, Self::HasNoChildren)
| (Self::Marked, Self::Marked)
| (Self::Unmarked, Self::Unmarked)
| (Self::Subscribed, Self::Subscribed)
| (Self::Remote, Self::Remote)
| (Self::All, Self::All)
| (Self::Archive, Self::Archive)
| (Self::Drafts, Self::Drafts)
| (Self::Flagged, Self::Flagged)
| (Self::Junk, Self::Junk)
| (Self::Sent, Self::Sent)
| (Self::Trash, Self::Trash)
| (Self::Important, Self::Important)
| (Self::NoAccess, Self::NoAccess)
| (Self::Memos, Self::Memos)
| (Self::Scheduled, Self::Scheduled)
| (Self::Snoozed, Self::Snoozed) => true,
(Self::Custom(a), Self::Custom(b)) => a.eq_ignore_ascii_case(b),
(Self::Custom(s), known) | (known, Self::Custom(s)) => {
s.eq_ignore_ascii_case(known.as_imap_str())
}
_ => false,
}
}
}
impl Eq for MailboxAttribute {}
impl std::hash::Hash for MailboxAttribute {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
for byte in self.as_imap_str().as_bytes() {
byte.to_ascii_lowercase().hash(state);
}
}
}
impl MailboxAttribute {
pub fn from_imap_str(s: &str) -> Self {
let lower = s.to_ascii_lowercase();
match lower.as_str() {
"\\noselect" | "\\nonexistent" => {
if lower == "\\nonexistent" {
Self::NonExistent
} else {
Self::NoSelect
}
}
"\\noinferiors" => Self::NoInferiors,
"\\haschildren" => Self::HasChildren,
"\\hasnochildren" => Self::HasNoChildren,
"\\marked" => Self::Marked,
"\\unmarked" => Self::Unmarked,
"\\subscribed" => Self::Subscribed,
"\\remote" => Self::Remote,
"\\all" => Self::All,
"\\archive" => Self::Archive,
"\\drafts" => Self::Drafts,
"\\flagged" => Self::Flagged,
"\\junk" => Self::Junk,
"\\sent" => Self::Sent,
"\\trash" => Self::Trash,
"\\important" => Self::Important,
"\\noaccess" => Self::NoAccess,
"\\memos" => Self::Memos,
"\\scheduled" => Self::Scheduled,
"\\snoozed" => Self::Snoozed,
_ => Self::Custom(s.to_owned()),
}
}
}
impl From<String> for MailboxAttribute {
fn from(s: String) -> Self {
Self::from_imap_str(&s)
}
}
impl From<&str> for MailboxAttribute {
fn from(s: &str) -> Self {
Self::from_imap_str(s)
}
}
impl std::fmt::Display for MailboxAttribute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_imap_str())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SpecialUse {
Inbox,
All,
Archive,
Drafts,
Sent,
Junk,
Trash,
Flagged,
Important,
}
impl MailboxInfo {
pub fn special_use(&self) -> Option<SpecialUse> {
for attr in &self.attributes {
match attr {
MailboxAttribute::All => return Some(SpecialUse::All),
MailboxAttribute::Archive => return Some(SpecialUse::Archive),
MailboxAttribute::Drafts => return Some(SpecialUse::Drafts),
MailboxAttribute::Sent => return Some(SpecialUse::Sent),
MailboxAttribute::Junk => return Some(SpecialUse::Junk),
MailboxAttribute::Trash => return Some(SpecialUse::Trash),
MailboxAttribute::Flagged => return Some(SpecialUse::Flagged),
MailboxAttribute::Important => return Some(SpecialUse::Important),
_ => {}
}
}
let lower = self.name.as_str().to_ascii_lowercase();
let leaf = match self.delimiter {
Some(delim) => match lower.rsplit(delim).next() {
Some(s) => s,
None => &lower,
},
None => &lower,
};
match leaf {
"inbox" => Some(SpecialUse::Inbox),
"all" | "all mail" => Some(SpecialUse::All),
"sent" | "sent items" | "sent messages" => Some(SpecialUse::Sent),
"drafts" | "draft" => Some(SpecialUse::Drafts),
"trash" | "deleted" | "deleted items" | "deleted messages" => Some(SpecialUse::Trash),
"spam" | "junk" | "junk e-mail" | "bulk mail" => Some(SpecialUse::Junk),
"archive" | "archives" => Some(SpecialUse::Archive),
"flagged" | "starred" => Some(SpecialUse::Flagged),
"important" => Some(SpecialUse::Important),
_ => None,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SelectedMailbox {
pub exists: u32,
pub recent: u32,
pub uid_validity: Option<u32>,
pub uid_next: Option<u32>,
pub flags: Vec<Flag>,
pub permanent_flags: Vec<Flag>,
pub highest_mod_seq: Option<u64>,
pub no_mod_seq: bool,
pub unseen: Option<u32>,
pub mailbox_id: Option<String>,
pub read_only: bool,
pub uid_not_sticky: bool,
pub vanished: Vec<UidRange>,
pub changed_messages: Vec<FetchResponse>,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum StatusItem {
Messages(u32),
Recent(u32),
Unseen(u32),
UidNext(u32),
UidValidity(u32),
Deleted(u32),
HighestModSeq(u64),
Size(u64),
MailboxId(String),
AppendLimit(Option<u64>),
DeletedStorage(u64),
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StatusResult {
pub items: Vec<StatusItem>,
pub ambiguous: Vec<Vec<StatusItem>>,
}
#[cfg(test)]
#[path = "mailbox_tests.rs"]
mod tests;