pub mod add;
pub mod config;
pub mod delete;
mod error;
pub mod expunge;
#[cfg(feature = "imap")]
pub mod imap;
pub mod list;
#[cfg(feature = "maildir")]
pub mod maildir;
pub mod purge;
#[cfg(feature = "sync")]
pub mod sync;
use std::{
fmt,
hash::Hash,
ops::{Deref, DerefMut},
str::FromStr,
};
#[cfg(feature = "sync")]
pub(crate) use sync::sync;
#[doc(inline)]
pub use self::error::{Error, Result};
pub const INBOX: &str = "INBOX";
pub const SENT: &str = "Sent";
pub const DRAFT: &str = "Drafts";
pub const DRAFTS: &str = "Drafts";
pub const TRASH: &str = "Trash";
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum FolderKind {
Inbox,
Sent,
Drafts,
Trash,
UserDefined(String),
}
impl FolderKind {
pub fn is_inbox(&self) -> bool {
matches!(self, FolderKind::Inbox)
}
pub fn is_sent(&self) -> bool {
matches!(self, FolderKind::Sent)
}
pub fn is_drafts(&self) -> bool {
matches!(self, FolderKind::Drafts)
}
pub fn is_trash(&self) -> bool {
matches!(self, FolderKind::Trash)
}
pub fn is_user_defined(&self) -> bool {
matches!(self, FolderKind::UserDefined(_))
}
pub fn matches_inbox(folder: impl AsRef<str>) -> bool {
folder
.as_ref()
.parse::<FolderKind>()
.map(|kind| kind.is_inbox())
.unwrap_or_default()
}
pub fn matches_sent(folder: impl AsRef<str>) -> bool {
folder
.as_ref()
.parse::<FolderKind>()
.map(|kind| kind.is_sent())
.unwrap_or_default()
}
pub fn matches_drafts(folder: impl AsRef<str>) -> bool {
folder
.as_ref()
.parse::<FolderKind>()
.map(|kind| kind.is_drafts())
.unwrap_or_default()
}
pub fn matches_trash(folder: impl AsRef<str>) -> bool {
folder
.as_ref()
.parse::<FolderKind>()
.map(|kind| kind.is_trash())
.unwrap_or_default()
}
pub fn as_str(&self) -> &str {
match self {
Self::Inbox => INBOX,
Self::Sent => SENT,
Self::Drafts => DRAFTS,
Self::Trash => TRASH,
Self::UserDefined(alias) => alias.as_str(),
}
}
}
impl FromStr for FolderKind {
type Err = Error;
fn from_str(kind: &str) -> Result<Self> {
match kind {
kind if kind.eq_ignore_ascii_case(INBOX) => Ok(Self::Inbox),
kind if kind.eq_ignore_ascii_case(SENT) => Ok(Self::Sent),
kind if kind.eq_ignore_ascii_case(DRAFT) => Ok(Self::Drafts),
kind if kind.eq_ignore_ascii_case(DRAFTS) => Ok(Self::Drafts),
kind if kind.eq_ignore_ascii_case(TRASH) => Ok(Self::Trash),
kind => Err(Error::ParseFolderKindError(kind.to_owned())),
}
}
}
impl<T: AsRef<str>> From<T> for FolderKind {
fn from(kind: T) -> Self {
kind.as_ref()
.parse()
.ok()
.unwrap_or_else(|| Self::UserDefined(kind.as_ref().to_owned()))
}
}
impl fmt::Display for FolderKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Clone, Debug, Default, Eq)]
pub struct Folder {
pub kind: Option<FolderKind>,
pub name: String,
pub desc: String,
}
impl Folder {
pub fn is_inbox(&self) -> bool {
self.kind
.as_ref()
.map(|kind| kind.is_inbox())
.unwrap_or_default()
}
pub fn is_sent(&self) -> bool {
self.kind
.as_ref()
.map(|kind| kind.is_sent())
.unwrap_or_default()
}
pub fn is_drafts(&self) -> bool {
self.kind
.as_ref()
.map(|kind| kind.is_drafts())
.unwrap_or_default()
}
pub fn is_trash(&self) -> bool {
self.kind
.as_ref()
.map(|kind| kind.is_trash())
.unwrap_or_default()
}
pub fn get_kind_or_name(&self) -> &str {
self.kind
.as_ref()
.map(FolderKind::as_str)
.unwrap_or(self.name.as_str())
}
}
impl PartialEq for Folder {
fn eq(&self, other: &Self) -> bool {
match (&self.kind, &other.kind) {
(Some(self_kind), Some(other_kind)) => self_kind == other_kind,
(None, None) => self.name == other.name,
_ => false,
}
}
}
impl Hash for Folder {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match &self.kind {
Some(kind) => kind.hash(state),
None => self.name.hash(state),
}
}
}
impl fmt::Display for Folder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.kind {
Some(kind) => write!(f, "{kind}"),
None => write!(f, "{}", self.name),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Folders(Vec<Folder>);
impl Deref for Folders {
type Target = Vec<Folder>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Folders {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl IntoIterator for Folders {
type Item = Folder;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl FromIterator<Folder> for Folders {
fn from_iter<T: IntoIterator<Item = Folder>>(iter: T) -> Self {
let mut folders = Folders::default();
folders.extend(iter);
folders
}
}
impl From<Folders> for Vec<Folder> {
fn from(val: Folders) -> Self {
val.0
}
}
#[cfg(test)]
mod tests {
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
use super::*;
fn folder_inbox_foo() -> Folder {
Folder {
kind: Some(FolderKind::Inbox),
name: "foo".to_owned(),
desc: "1".to_owned(),
}
}
fn folder_none_foo() -> Folder {
Folder {
kind: None,
name: "foo".to_owned(),
desc: "2".to_owned(),
}
}
fn folder_none_bar() -> Folder {
Folder {
kind: None,
name: "bar".to_owned(),
desc: "3".to_owned(),
}
}
fn folder_inbox_bar() -> Folder {
Folder {
kind: Some(FolderKind::Inbox),
name: "bar".to_owned(),
desc: "4".to_owned(),
}
}
fn hash<H: Hash>(item: H) -> u64 {
let mut hasher = DefaultHasher::new();
item.hash(&mut hasher);
hasher.finish()
}
#[test]
fn folder_inbox_bar_equals_inbox_foo_test() {
assert_eq!(folder_inbox_bar(), folder_inbox_foo());
}
#[test]
fn folder_inbox_bar_equals_inbox_foo_test_hash() {
assert_eq!(hash(folder_inbox_bar()), hash(folder_inbox_foo()));
}
#[test]
fn folder_none_foo_not_equals_inbox_foo_test() {
assert_ne!(folder_none_foo(), folder_inbox_foo());
}
#[test]
fn folder_none_foo_not_equals_inbox_foo_test_hash() {
assert_ne!(hash(folder_none_foo()), hash(folder_inbox_foo()));
}
#[test]
fn folder_none_foo_not_equals_none_bar_test() {
assert_ne!(folder_none_foo(), folder_none_bar());
}
#[test]
fn folder_none_foo_not_equals_none_bar_test_hash() {
assert_ne!(hash(folder_none_foo()), hash(folder_none_bar()));
}
}