use std::cmp::PartialEq;
use std::fmt;
use std::iter::{FromIterator, IntoIterator, Iterator};
use std::ops::Deref;
#[cfg(feature = "mailparse-conversions")]
use super::error::Error;
#[cfg(feature = "mailparse-conversions")]
use std::convert::TryInto;
pub trait DeepEq<Rhs = Self> {
fn deep_eq(&self, other: &Rhs) -> bool;
fn deep_ne(&self, other: &Rhs) -> bool {
!self.deep_eq(other)
}
}
pub trait Contactish {
fn email(&self) -> Option<&String>;
fn name(&self) -> Option<&String>;
fn comment(&self) -> Option<&String>;
fn new<T>(required: T) -> Self
where
T: AsRef<str>;
fn set_name<T>(self, name: T) -> Self
where
T: AsRef<str>;
fn set_email<T>(self, email: T) -> Self
where
T: AsRef<str>;
fn set_comment<T>(self, comment: T) -> Self
where
T: AsRef<str>;
fn to_contact(self) -> Contact;
}
pub trait Contactsish {
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
fn to_contacts(self) -> Contacts;
fn add<C>(&mut self, contact: C)
where
C: Contactish;
fn contains(&self, contact: &Contact) -> bool;
}
#[derive(Debug, Clone, Default)]
pub struct EmailContact {
email: String,
name: Option<String>,
comment: Option<String>,
}
impl Contactish for EmailContact {
fn email(&self) -> Option<&String> {
Some(&self.email)
}
fn name(&self) -> Option<&String> {
self.name.as_ref()
}
fn comment(&self) -> Option<&String> {
self.comment.as_ref()
}
fn new<T>(email: T) -> Self
where
T: AsRef<str>,
{
EmailContact {
email: email.as_ref().into(),
name: None,
comment: None,
}
}
fn set_name<T>(mut self, name: T) -> Self
where
T: AsRef<str>,
{
let name = name.as_ref().trim();
if !name.is_empty() {
self.name = Some(name.into());
}
self
}
fn set_email<T>(mut self, email: T) -> Self
where
T: AsRef<str>,
{
self.email = email.as_ref().into();
self
}
fn set_comment<T>(mut self, comment: T) -> Self
where
T: AsRef<str>,
{
let comment = comment.as_ref();
if !comment.is_empty() {
self.comment = Some(comment.into());
}
self
}
fn to_contact(self) -> Contact {
Contact::from(self)
}
}
impl PartialEq for EmailContact {
fn eq(&self, other: &EmailContact) -> bool {
self.email == other.email
}
}
impl DeepEq for EmailContact {
fn deep_eq(&self, other: &EmailContact) -> bool {
self.email == other.email && self.name == other.name && self.comment == other.comment
}
}
impl fmt::Display for EmailContact {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(n) = &self.name {
write!(f, "\"{}\" ", n.replace('\\', "\\\\").replace('"', "\\\""))?;
if let Some(c) = &self.comment {
write!(f, "({}) ", c)?;
}
}
write!(
f,
"<{}>",
self.email.replace('\\', "\\\\").replace('"', "\\\""),
)
}
}
#[derive(Debug, Clone, Default)]
pub struct GarbageContact(String);
impl Contactish for GarbageContact {
fn email(&self) -> Option<&String> {
None
}
fn name(&self) -> Option<&String> {
None
}
fn comment(&self) -> Option<&String> {
Some(&self.0)
}
fn new<T>(garbage: T) -> Self
where
T: AsRef<str>,
{
GarbageContact(garbage.as_ref().into())
}
fn set_comment<T>(mut self, garbage: T) -> Self
where
T: AsRef<str>,
{
self.0 = garbage.as_ref().into();
self
}
fn set_email<T>(self, _: T) -> Self {
self
}
fn set_name<T>(self, _: T) -> Self {
self
}
fn to_contact(self) -> Contact {
Contact::from(self)
}
}
impl From<String> for GarbageContact {
fn from(string: String) -> Self {
GarbageContact(string)
}
}
#[derive(Clone)]
pub enum Contact {
Email(EmailContact),
Garbage(GarbageContact),
}
impl Contact {
pub fn is_garbage(&self) -> bool {
matches!(self, Contact::Garbage(_))
}
}
impl Contactish for Contact {
fn name(&self) -> Option<&String> {
match self {
Contact::Email(c) => c.name(),
Contact::Garbage(_) => None,
}
}
fn email(&self) -> Option<&String> {
match self {
Contact::Email(c) => c.email(),
Contact::Garbage(_) => None,
}
}
fn comment(&self) -> Option<&String> {
match self {
Contact::Email(c) => c.comment(),
Contact::Garbage(c) => c.comment(),
}
}
fn new<T>(email: T) -> Self
where
T: AsRef<str>,
{
EmailContact::new(email).into()
}
fn set_name<T>(self, name: T) -> Self
where
T: AsRef<str>,
{
match self {
Contact::Email(c) => c.set_name(name).into(),
Contact::Garbage(g) => g.set_name(name).into(),
}
}
fn set_comment<T>(self, comment: T) -> Self
where
T: AsRef<str>,
{
match self {
Contact::Email(c) => c.set_comment(comment).into(),
Contact::Garbage(g) => g.set_comment(comment).into(),
}
}
fn set_email<T>(self, email: T) -> Self
where
T: AsRef<str>,
{
match self {
Contact::Email(c) => c.set_email(email).into(),
Contact::Garbage(g) => g.set_email(email).into(),
}
}
fn to_contact(self) -> Self {
self
}
}
impl PartialEq for Contact {
fn eq(&self, other: &Contact) -> bool {
self.email() == other.email()
}
}
impl DeepEq for Contact {
fn deep_eq(&self, other: &Contact) -> bool {
self.email() == other.email()
|| self.name() == other.name()
|| self.comment() == other.comment()
}
}
impl fmt::Debug for Contact {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Contact({}{}{})",
match self.name() {
Some(n) => format!("\"{}\" ", n),
None => "".into(),
},
match self.comment() {
Some(c) => {
if !self.is_garbage() {
format!("({}) ", c)
} else {
format!("Garbage: \"{}\"", c)
}
}
None => "".into(),
},
match self.email() {
Some(e) => format!("<{}>", e),
None => "".into(),
}
)
}
}
impl fmt::Display for Contact {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Contact::Garbage(_) => write!(f, ""),
Contact::Email(e) => write!(f, "{}", e),
}
}
}
impl From<GarbageContact> for Contact {
fn from(garbage: GarbageContact) -> Contact {
Contact::Garbage(garbage)
}
}
impl From<EmailContact> for Contact {
fn from(contact: EmailContact) -> Contact {
Contact::Email(contact)
}
}
#[cfg(feature = "mailparse-conversions")]
impl TryInto<mailparse::MailAddr> for Contact {
type Error = Error;
fn try_into(self) -> Result<mailparse::MailAddr, Error> {
match self {
Contact::Garbage(_) => Err(Error::UnexpectedError(
"Can't convert Garbage into MailAddr".into(),
)),
Contact::Email(_) => Ok(mailparse::MailAddr::Single(self.try_into()?)),
}
}
}
#[cfg(feature = "mailparse-conversions")]
impl TryInto<mailparse::SingleInfo> for Contact {
type Error = Error;
fn try_into(self) -> Result<mailparse::SingleInfo, Error> {
match self {
Contact::Garbage(_) => Err(Error::UnexpectedError(
"Can't convert Garbage into SingleInfo".into(),
)),
Contact::Email(e) => Ok(mailparse::SingleInfo {
display_name: e.name,
addr: e.email,
}),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Contacts {
pub contacts: Vec<Contact>,
}
impl Contacts {
pub fn new() -> Self {
Self {
contacts: Vec::new(),
}
}
}
impl Contactsish for Vec<Contact> {
fn len(&self) -> usize {
self.len()
}
fn is_empty(&self) -> bool {
self.is_empty()
}
fn to_contacts(self) -> Contacts {
Contacts::from(self)
}
fn add<C>(&mut self, contact: C)
where
C: Contactish,
{
self.push(contact.to_contact());
}
fn contains(&self, contact: &Contact) -> bool {
self.iter().any(|y| y == contact)
}
}
impl Contactsish for Contacts {
fn len(&self) -> usize {
self.contacts.len()
}
fn is_empty(&self) -> bool {
self.contacts.is_empty()
}
fn to_contacts(self) -> Contacts {
self
}
fn add<C>(&mut self, contact: C)
where
C: Contactish,
{
self.contacts.push(contact.to_contact());
}
fn contains(&self, contact: &Contact) -> bool {
self.contacts.contains(contact)
}
}
impl Deref for Contacts {
type Target = [Contact];
fn deref(&self) -> &[Contact] {
self.contacts.as_slice()
}
}
impl<'a> IntoIterator for &'a Contacts {
type Item = &'a Contact;
type IntoIter = std::slice::Iter<'a, Contact>;
fn into_iter(self) -> Self::IntoIter {
self.contacts.iter()
}
}
impl IntoIterator for Contacts {
type Item = Contact;
type IntoIter = std::vec::IntoIter<Contact>;
fn into_iter(self) -> Self::IntoIter {
self.contacts.into_iter()
}
}
impl FromIterator<Contact> for Contacts {
fn from_iter<I: IntoIterator<Item = Contact>>(iter: I) -> Contacts {
let mut contacts = Contacts::new();
contacts.contacts = Vec::<Contact>::from_iter(iter);
contacts
}
}
impl From<Vec<Contact>> for Contacts {
fn from(s: Vec<Contact>) -> Self {
Self { contacts: s }
}
}
impl fmt::Display for Contacts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let trim: &[_] = &[' ', ','];
write!(
f,
"{}",
self.contacts
.iter()
.map(|c| format!("{}", c))
.collect::<Vec<String>>()
.join(", ")
.trim_matches(trim),
)
}
}
#[cfg(feature = "mailparse-conversions")]
impl TryInto<Vec<mailparse::SingleInfo>> for Contacts {
type Error = Error;
fn try_into(self) -> Result<Vec<mailparse::SingleInfo>, Error> {
self.into_iter().map(|c| c.try_into()).collect()
}
}
#[derive(Debug, Clone, Default)]
pub struct Group {
pub name: String,
pub contacts: Contacts,
}
impl Group {
pub fn new<T>(name: T) -> Self
where
T: AsRef<str>,
{
Self {
name: name.as_ref().into(),
..Default::default()
}
}
pub fn set_contacts<T>(mut self, contacts: T) -> Self
where
T: Contactsish,
{
self.contacts = contacts.to_contacts();
self
}
}
impl PartialEq for Group {
fn eq(&self, other: &Group) -> bool {
if self.name != other.name || self.contacts.len() != other.contacts.len() {
return false;
}
for (i, contact) in self.contacts.iter().enumerate() {
if contact != &other.contacts[i] {
return false;
}
}
true
}
}
impl DeepEq for Group {
fn deep_eq(&self, other: &Group) -> bool {
if self.name != other.name || self.contacts.len() != other.contacts.len() {
return false;
}
for (i, contact) in self.contacts.iter().enumerate() {
if !contact.deep_eq(&other.contacts[i]) {
return false;
}
}
true
}
}
impl<T> From<T> for Group
where
T: AsRef<str>,
{
fn from(string: T) -> Self {
Self {
name: string.as_ref().into(),
contacts: Contacts::new(),
}
}
}
impl Contactsish for Group {
fn len(&self) -> usize {
self.contacts.len()
}
fn is_empty(&self) -> bool {
self.contacts.is_empty()
}
fn to_contacts(self) -> Contacts {
self.contacts
}
fn add<C>(&mut self, contact: C)
where
C: Contactish,
{
self.contacts.add(contact.to_contact());
}
fn contains(&self, contact: &Contact) -> bool {
self.contacts.contains(contact)
}
}
#[cfg(feature = "mailparse-conversions")]
impl TryInto<mailparse::MailAddr> for Group {
type Error = Error;
fn try_into(self) -> Result<mailparse::MailAddr, Error> {
Ok(mailparse::MailAddr::Group(mailparse::GroupInfo {
group_name: self.name,
addrs: self
.contacts
.into_iter()
.map(|c| c.try_into())
.collect::<Result<Vec<_>, Error>>()?,
}))
}
}
impl fmt::Display for Group {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"\"{}\": {};",
self.name.replace('\\', "\\\\").replace('"', "\\\""),
self.contacts
)
}
}
#[derive(Debug, Clone)]
pub enum AddressList {
Contacts(Contacts),
Group(Group),
}
impl AddressList {
pub fn is_group(&self) -> bool {
matches!(self, AddressList::Group(_))
}
pub fn group_name(&self) -> Option<&String> {
match self {
AddressList::Group(g) => Some(&g.name),
_ => None,
}
}
pub fn contacts(&self) -> &Contacts {
match self {
AddressList::Contacts(c) => c,
AddressList::Group(g) => &g.contacts,
}
}
}
impl fmt::Display for AddressList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AddressList::Contacts(c) => write!(f, "{}", c),
AddressList::Group(g) => write!(f, "{}", g),
}
}
}
impl PartialEq for AddressList {
fn eq(&self, other: &AddressList) -> bool {
if self.is_group() != other.is_group() {
return false;
}
match self {
AddressList::Group(g) => {
if let AddressList::Group(o) = other {
g == o
} else {
false
}
}
AddressList::Contacts(c) => {
if let AddressList::Contacts(o) = other {
if c.len() != o.len() {
return false;
}
for (i, contact) in c.iter().enumerate() {
if contact != &o[i] {
return false;
}
}
true
} else {
false
}
}
}
}
}
impl DeepEq for AddressList {
fn deep_eq(&self, other: &AddressList) -> bool {
if self.is_group() != other.is_group() {
return false;
}
match self {
AddressList::Group(g) => {
if let AddressList::Group(o) = other {
g.deep_eq(o)
} else {
false
}
}
AddressList::Contacts(c) => {
if let AddressList::Contacts(o) = other {
if c.len() != o.len() {
return false;
}
for (i, contact) in c.iter().enumerate() {
if !contact.deep_eq(&o[i]) {
return false;
}
}
true
} else {
false
}
}
}
}
}
impl From<Vec<Contact>> for AddressList {
fn from(s: Vec<Contact>) -> Self {
Self::Contacts(Contacts { contacts: s })
}
}
impl Contactsish for AddressList {
fn len(&self) -> usize {
match self {
Self::Contacts(c) => c.len(),
Self::Group(g) => g.contacts.len(),
}
}
fn is_empty(&self) -> bool {
match self {
Self::Contacts(c) => c.is_empty(),
Self::Group(g) => g.contacts.is_empty(),
}
}
fn to_contacts(self) -> Contacts {
match self {
Self::Contacts(c) => c,
Self::Group(g) => g.contacts,
}
}
fn add<C>(&mut self, contact: C)
where
C: Contactish,
{
match self {
Self::Contacts(c) => c.add(contact),
Self::Group(g) => g.add(contact),
}
}
fn contains(&self, contact: &Contact) -> bool {
match self {
Self::Contacts(c) => c.contains(contact),
Self::Group(g) => g.contains(contact),
}
}
}
impl From<Contacts> for AddressList {
fn from(contacts: Contacts) -> Self {
Self::Contacts(contacts)
}
}
impl From<Group> for AddressList {
fn from(group: Group) -> AddressList {
Self::Group(group)
}
}
#[cfg(feature = "mailparse-conversions")]
impl TryInto<Vec<mailparse::MailAddr>> for AddressList {
type Error = Error;
fn try_into(self) -> Result<Vec<mailparse::MailAddr>, Error> {
match self {
Self::Group(g) => Ok(vec![g.try_into()?]),
Self::Contacts(c) => c.into_iter().map(|ic| ic.try_into()).collect(),
}
}
}