email_address_list/
address_list.rs

1use std::cmp::PartialEq;
2use std::fmt;
3use std::iter::{FromIterator, IntoIterator, Iterator};
4use std::ops::Deref;
5
6#[cfg(feature = "mailparse-conversions")]
7use super::error::Error;
8#[cfg(feature = "mailparse-conversions")]
9use std::convert::TryInto;
10
11/// Check if all fields are the same rather than just a subset ("deep equals")
12pub trait DeepEq<Rhs = Self> {
13    fn deep_eq(&self, other: &Rhs) -> bool;
14    fn deep_ne(&self, other: &Rhs) -> bool {
15        !self.deep_eq(other)
16    }
17}
18
19/// Unified interface for all contact types
20pub trait Contactish {
21    fn email(&self) -> Option<&String>;
22    fn name(&self) -> Option<&String>;
23    fn comment(&self) -> Option<&String>;
24    fn new<T>(required: T) -> Self
25    where
26        T: AsRef<str>;
27    fn set_name<T>(self, name: T) -> Self
28    where
29        T: AsRef<str>;
30    fn set_email<T>(self, email: T) -> Self
31    where
32        T: AsRef<str>;
33    fn set_comment<T>(self, comment: T) -> Self
34    where
35        T: AsRef<str>;
36    fn to_contact(self) -> Contact;
37}
38
39/// For everything that has contacts
40pub trait Contactsish {
41    fn len(&self) -> usize;
42    fn is_empty(&self) -> bool;
43    fn to_contacts(self) -> Contacts;
44    fn add<C>(&mut self, contact: C)
45    where
46        C: Contactish;
47    fn contains(&self, contact: &Contact) -> bool;
48}
49
50/// A contact with at least an email address
51#[derive(Debug, Clone, Default)]
52pub struct EmailContact {
53    email: String,
54    name: Option<String>,
55    comment: Option<String>,
56}
57
58impl Contactish for EmailContact {
59    fn email(&self) -> Option<&String> {
60        Some(&self.email)
61    }
62
63    fn name(&self) -> Option<&String> {
64        self.name.as_ref()
65    }
66
67    fn comment(&self) -> Option<&String> {
68        self.comment.as_ref()
69    }
70
71    fn new<T>(email: T) -> Self
72    where
73        T: AsRef<str>,
74    {
75        EmailContact {
76            email: email.as_ref().into(),
77            name: None,
78            comment: None,
79        }
80    }
81
82    fn set_name<T>(mut self, name: T) -> Self
83    where
84        T: AsRef<str>,
85    {
86        let name = name.as_ref().trim();
87        if !name.is_empty() {
88            self.name = Some(name.into());
89        }
90        self
91    }
92
93    fn set_email<T>(mut self, email: T) -> Self
94    where
95        T: AsRef<str>,
96    {
97        self.email = email.as_ref().into();
98        self
99    }
100
101    fn set_comment<T>(mut self, comment: T) -> Self
102    where
103        T: AsRef<str>,
104    {
105        let comment = comment.as_ref();
106        if !comment.is_empty() {
107            self.comment = Some(comment.into());
108        }
109        self
110    }
111
112    fn to_contact(self) -> Contact {
113        Contact::from(self)
114    }
115}
116
117/// Check if the email field is the same
118impl PartialEq for EmailContact {
119    fn eq(&self, other: &EmailContact) -> bool {
120        self.email == other.email
121    }
122}
123
124/// Check if all fields are the same (PartialEq only checks if email is the
125/// same)
126impl DeepEq for EmailContact {
127    fn deep_eq(&self, other: &EmailContact) -> bool {
128        self.email == other.email && self.name == other.name && self.comment == other.comment
129    }
130}
131
132impl fmt::Display for EmailContact {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        if let Some(n) = &self.name {
135            write!(f, "\"{}\" ", n.replace('\\', "\\\\").replace('"', "\\\""))?;
136            if let Some(c) = &self.comment {
137                write!(f, "({}) ", c)?;
138            }
139        }
140        write!(
141            f,
142            "<{}>",
143            self.email.replace('\\', "\\\\").replace('"', "\\\""),
144        )
145    }
146}
147
148/// A string that we couldn't parse into an [`EmailContact`] but implements
149/// the [`Contactish`] trait regardless
150///
151/// [`EmailContact`]: struct.EmailContact.html
152/// [`Contactish`]: trait.Contactish.html
153#[derive(Debug, Clone, Default)]
154pub struct GarbageContact(String);
155
156impl Contactish for GarbageContact {
157    /// Since we are garbage, we don't have an email address
158    fn email(&self) -> Option<&String> {
159        None
160    }
161
162    /// Since we are garbage, we don't have a name
163    fn name(&self) -> Option<&String> {
164        None
165    }
166
167    /// Returns the actual string we couldn't interpret as [`EmailContact`]
168    ///
169    /// [`EmailContact`]: struct.EmailContact.html
170    fn comment(&self) -> Option<&String> {
171        Some(&self.0)
172    }
173
174    fn new<T>(garbage: T) -> Self
175    where
176        T: AsRef<str>,
177    {
178        GarbageContact(garbage.as_ref().into())
179    }
180
181    fn set_comment<T>(mut self, garbage: T) -> Self
182    where
183        T: AsRef<str>,
184    {
185        self.0 = garbage.as_ref().into();
186        self
187    }
188
189    fn set_email<T>(self, _: T) -> Self {
190        self
191    }
192
193    fn set_name<T>(self, _: T) -> Self {
194        self
195    }
196
197    fn to_contact(self) -> Contact {
198        Contact::from(self)
199    }
200}
201
202impl From<String> for GarbageContact {
203    fn from(string: String) -> Self {
204        GarbageContact(string)
205    }
206}
207
208/// Either an [`EmailContact`] we could successfully parse or a
209/// [`GarbageContact`] we didn't want to throw away
210///
211/// [`EmailContact`]: struct.EmailContact.html
212/// [`GarbageContact`]: struct.GarbageContact.html
213#[derive(Clone)]
214pub enum Contact {
215    Email(EmailContact),
216    Garbage(GarbageContact),
217}
218
219impl Contact {
220    pub fn is_garbage(&self) -> bool {
221        matches!(self, Contact::Garbage(_))
222    }
223}
224
225/// Will be handed down on our variants' contents, which implement the same
226/// trait
227///
228/// The exception to the rule is the [`::new`] method.
229///
230/// **Please note:** the current implementation does not (yet?) magically change
231/// a `Contact::Garbage` variant into a `Contact::Email` one if you try to call
232/// `::set_email`. It merely returns an unchanged `Self`.
233///
234/// [`::new`]: enum.Contact.html#method.new
235impl Contactish for Contact {
236    fn name(&self) -> Option<&String> {
237        match self {
238            Contact::Email(c) => c.name(),
239            Contact::Garbage(_) => None,
240        }
241    }
242
243    fn email(&self) -> Option<&String> {
244        match self {
245            Contact::Email(c) => c.email(),
246            Contact::Garbage(_) => None,
247        }
248    }
249
250    fn comment(&self) -> Option<&String> {
251        match self {
252            Contact::Email(c) => c.comment(),
253            Contact::Garbage(c) => c.comment(),
254        }
255    }
256
257    /// By default we create a new `Contact::Email` variant, since
258    /// `Contact::Garbage` is merely a fallback
259    fn new<T>(email: T) -> Self
260    where
261        T: AsRef<str>,
262    {
263        EmailContact::new(email).into()
264    }
265
266    fn set_name<T>(self, name: T) -> Self
267    where
268        T: AsRef<str>,
269    {
270        match self {
271            Contact::Email(c) => c.set_name(name).into(),
272            Contact::Garbage(g) => g.set_name(name).into(),
273        }
274    }
275
276    fn set_comment<T>(self, comment: T) -> Self
277    where
278        T: AsRef<str>,
279    {
280        match self {
281            Contact::Email(c) => c.set_comment(comment).into(),
282            Contact::Garbage(g) => g.set_comment(comment).into(),
283        }
284    }
285
286    fn set_email<T>(self, email: T) -> Self
287    where
288        T: AsRef<str>,
289    {
290        match self {
291            Contact::Email(c) => c.set_email(email).into(),
292            Contact::Garbage(g) => g.set_email(email).into(),
293        }
294    }
295
296    fn to_contact(self) -> Self {
297        self
298    }
299}
300
301impl PartialEq for Contact {
302    fn eq(&self, other: &Contact) -> bool {
303        self.email() == other.email()
304    }
305}
306
307impl DeepEq for Contact {
308    fn deep_eq(&self, other: &Contact) -> bool {
309        self.email() == other.email()
310            || self.name() == other.name()
311            || self.comment() == other.comment()
312    }
313}
314
315impl fmt::Debug for Contact {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        write!(
318            f,
319            "Contact({}{}{})",
320            match self.name() {
321                Some(n) => format!("\"{}\" ", n),
322                None => "".into(),
323            },
324            match self.comment() {
325                Some(c) => {
326                    if !self.is_garbage() {
327                        format!("({}) ", c)
328                    } else {
329                        format!("Garbage: \"{}\"", c)
330                    }
331                }
332                None => "".into(),
333            },
334            match self.email() {
335                Some(e) => format!("<{}>", e),
336                None => "".into(),
337            }
338        )
339    }
340}
341
342impl fmt::Display for Contact {
343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344        match self {
345            Contact::Garbage(_) => write!(f, ""),
346            Contact::Email(e) => write!(f, "{}", e),
347        }
348    }
349}
350
351impl From<GarbageContact> for Contact {
352    fn from(garbage: GarbageContact) -> Contact {
353        Contact::Garbage(garbage)
354    }
355}
356
357impl From<EmailContact> for Contact {
358    fn from(contact: EmailContact) -> Contact {
359        Contact::Email(contact)
360    }
361}
362
363#[cfg(feature = "mailparse-conversions")]
364impl TryInto<mailparse::MailAddr> for Contact {
365    type Error = Error;
366
367    fn try_into(self) -> Result<mailparse::MailAddr, Error> {
368        match self {
369            Contact::Garbage(_) => Err(Error::UnexpectedError(
370                "Can't convert Garbage into MailAddr".into(),
371            )),
372            Contact::Email(_) => Ok(mailparse::MailAddr::Single(self.try_into()?)),
373        }
374    }
375}
376
377#[cfg(feature = "mailparse-conversions")]
378impl TryInto<mailparse::SingleInfo> for Contact {
379    type Error = Error;
380
381    fn try_into(self) -> Result<mailparse::SingleInfo, Error> {
382        match self {
383            Contact::Garbage(_) => Err(Error::UnexpectedError(
384                "Can't convert Garbage into SingleInfo".into(),
385            )),
386            Contact::Email(e) => Ok(mailparse::SingleInfo {
387                display_name: e.name,
388                addr: e.email,
389            }),
390        }
391    }
392}
393
394/// Container for [`Contact`]s
395///
396/// [`Contact`]: enum.Contact.html
397///
398#[derive(Debug, Clone, Default)]
399pub struct Contacts {
400    pub contacts: Vec<Contact>,
401}
402
403impl Contacts {
404    pub fn new() -> Self {
405        Self {
406            contacts: Vec::new(),
407        }
408    }
409}
410
411impl Contactsish for Vec<Contact> {
412    fn len(&self) -> usize {
413        self.len()
414    }
415
416    fn is_empty(&self) -> bool {
417        self.is_empty()
418    }
419
420    fn to_contacts(self) -> Contacts {
421        Contacts::from(self)
422    }
423
424    fn add<C>(&mut self, contact: C)
425    where
426        C: Contactish,
427    {
428        self.push(contact.to_contact());
429    }
430
431    fn contains(&self, contact: &Contact) -> bool {
432        self.iter().any(|y| y == contact)
433    }
434}
435
436impl Contactsish for Contacts {
437    fn len(&self) -> usize {
438        self.contacts.len()
439    }
440
441    fn is_empty(&self) -> bool {
442        self.contacts.is_empty()
443    }
444
445    fn to_contacts(self) -> Contacts {
446        self
447    }
448
449    fn add<C>(&mut self, contact: C)
450    where
451        C: Contactish,
452    {
453        self.contacts.push(contact.to_contact());
454    }
455
456    fn contains(&self, contact: &Contact) -> bool {
457        self.contacts.contains(contact)
458    }
459}
460
461impl Deref for Contacts {
462    type Target = [Contact];
463
464    fn deref(&self) -> &[Contact] {
465        self.contacts.as_slice()
466    }
467}
468
469impl<'a> IntoIterator for &'a Contacts {
470    type Item = &'a Contact;
471    type IntoIter = std::slice::Iter<'a, Contact>;
472
473    fn into_iter(self) -> Self::IntoIter {
474        self.contacts.iter()
475    }
476}
477
478impl IntoIterator for Contacts {
479    type Item = Contact;
480    type IntoIter = std::vec::IntoIter<Contact>;
481
482    fn into_iter(self) -> Self::IntoIter {
483        self.contacts.into_iter()
484    }
485}
486
487impl FromIterator<Contact> for Contacts {
488    fn from_iter<I: IntoIterator<Item = Contact>>(iter: I) -> Contacts {
489        let mut contacts = Contacts::new();
490        contacts.contacts = Vec::<Contact>::from_iter(iter);
491        contacts
492    }
493}
494
495impl From<Vec<Contact>> for Contacts {
496    fn from(s: Vec<Contact>) -> Self {
497        Self { contacts: s }
498    }
499}
500
501impl fmt::Display for Contacts {
502    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503        let trim: &[_] = &[' ', ','];
504        write!(
505            f,
506            "{}",
507            self.contacts
508                .iter()
509                .map(|c| format!("{}", c))
510                .collect::<Vec<String>>()
511                .join(", ")
512                .trim_matches(trim),
513        )
514    }
515}
516
517#[cfg(feature = "mailparse-conversions")]
518impl TryInto<Vec<mailparse::SingleInfo>> for Contacts {
519    type Error = Error;
520
521    fn try_into(self) -> Result<Vec<mailparse::SingleInfo>, Error> {
522        self.into_iter().map(|c| c.try_into()).collect()
523    }
524}
525
526/// A group with a name and [`Contacts`]
527///
528/// [`Contacts`]: struct.Contacts.html
529#[derive(Debug, Clone, Default)]
530pub struct Group {
531    pub name: String,
532    pub contacts: Contacts,
533}
534
535impl Group {
536    pub fn new<T>(name: T) -> Self
537    where
538        T: AsRef<str>,
539    {
540        Self {
541            name: name.as_ref().into(),
542            ..Default::default()
543        }
544    }
545
546    pub fn set_contacts<T>(mut self, contacts: T) -> Self
547    where
548        T: Contactsish,
549    {
550        self.contacts = contacts.to_contacts();
551        self
552    }
553}
554
555impl PartialEq for Group {
556    fn eq(&self, other: &Group) -> bool {
557        if self.name != other.name || self.contacts.len() != other.contacts.len() {
558            return false;
559        }
560        for (i, contact) in self.contacts.iter().enumerate() {
561            if contact != &other.contacts[i] {
562                return false;
563            }
564        }
565        true
566    }
567}
568
569impl DeepEq for Group {
570    fn deep_eq(&self, other: &Group) -> bool {
571        if self.name != other.name || self.contacts.len() != other.contacts.len() {
572            return false;
573        }
574        for (i, contact) in self.contacts.iter().enumerate() {
575            if !contact.deep_eq(&other.contacts[i]) {
576                return false;
577            }
578        }
579        true
580    }
581}
582
583impl<T> From<T> for Group
584where
585    T: AsRef<str>,
586{
587    fn from(string: T) -> Self {
588        Self {
589            name: string.as_ref().into(),
590            contacts: Contacts::new(),
591        }
592    }
593}
594
595impl Contactsish for Group {
596    fn len(&self) -> usize {
597        self.contacts.len()
598    }
599
600    fn is_empty(&self) -> bool {
601        self.contacts.is_empty()
602    }
603
604    fn to_contacts(self) -> Contacts {
605        self.contacts
606    }
607
608    fn add<C>(&mut self, contact: C)
609    where
610        C: Contactish,
611    {
612        self.contacts.add(contact.to_contact());
613    }
614
615    fn contains(&self, contact: &Contact) -> bool {
616        self.contacts.contains(contact)
617    }
618}
619
620#[cfg(feature = "mailparse-conversions")]
621impl TryInto<mailparse::MailAddr> for Group {
622    type Error = Error;
623
624    fn try_into(self) -> Result<mailparse::MailAddr, Error> {
625        Ok(mailparse::MailAddr::Group(mailparse::GroupInfo {
626            group_name: self.name,
627            addrs: self
628                .contacts
629                .into_iter()
630                .map(|c| c.try_into())
631                .collect::<Result<Vec<_>, Error>>()?,
632        }))
633    }
634}
635
636impl fmt::Display for Group {
637    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638        write!(
639            f,
640            "\"{}\": {};",
641            self.name.replace('\\', "\\\\").replace('"', "\\\""),
642            self.contacts
643        )
644    }
645}
646
647/// All forms which email headers like `To`, `From`, `Cc`, etc. can take
648///
649/// # Examples
650///
651/// ```rust
652/// # use email_address_list::*;
653/// let latvian: AddressList = vec![Contact::new("piemērs@example.org")].into();
654/// assert!(latvian.contacts()[0].email().unwrap() == "piemērs@example.org");
655///
656/// let sudanese: AddressList = Group::new("Conto").into();
657/// assert!(sudanese.group_name() == Some(&"Conto".to_string()));
658/// ```
659#[derive(Debug, Clone)]
660pub enum AddressList {
661    Contacts(Contacts),
662    Group(Group),
663}
664
665impl AddressList {
666    /// Check if this address list is a group
667    pub fn is_group(&self) -> bool {
668        matches!(self, AddressList::Group(_))
669    }
670
671    /// Get the group name if it is a group
672    pub fn group_name(&self) -> Option<&String> {
673        match self {
674            AddressList::Group(g) => Some(&g.name),
675            _ => None,
676        }
677    }
678
679    /// Get the contacts regardless of our variant
680    pub fn contacts(&self) -> &Contacts {
681        match self {
682            AddressList::Contacts(c) => c,
683            AddressList::Group(g) => &g.contacts,
684        }
685    }
686}
687
688impl fmt::Display for AddressList {
689    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
690        match self {
691            AddressList::Contacts(c) => write!(f, "{}", c),
692            AddressList::Group(g) => write!(f, "{}", g),
693        }
694    }
695}
696
697impl PartialEq for AddressList {
698    fn eq(&self, other: &AddressList) -> bool {
699        if self.is_group() != other.is_group() {
700            return false;
701        }
702        match self {
703            AddressList::Group(g) => {
704                if let AddressList::Group(o) = other {
705                    g == o
706                } else {
707                    false
708                }
709            }
710            AddressList::Contacts(c) => {
711                if let AddressList::Contacts(o) = other {
712                    if c.len() != o.len() {
713                        return false;
714                    }
715                    for (i, contact) in c.iter().enumerate() {
716                        if contact != &o[i] {
717                            return false;
718                        }
719                    }
720                    true
721                } else {
722                    false
723                }
724            }
725        }
726    }
727}
728
729impl DeepEq for AddressList {
730    fn deep_eq(&self, other: &AddressList) -> bool {
731        if self.is_group() != other.is_group() {
732            return false;
733        }
734        match self {
735            AddressList::Group(g) => {
736                if let AddressList::Group(o) = other {
737                    g.deep_eq(o)
738                } else {
739                    false
740                }
741            }
742            AddressList::Contacts(c) => {
743                if let AddressList::Contacts(o) = other {
744                    if c.len() != o.len() {
745                        return false;
746                    }
747                    for (i, contact) in c.iter().enumerate() {
748                        if !contact.deep_eq(&o[i]) {
749                            return false;
750                        }
751                    }
752                    true
753                } else {
754                    false
755                }
756            }
757        }
758    }
759}
760
761impl From<Vec<Contact>> for AddressList {
762    fn from(s: Vec<Contact>) -> Self {
763        Self::Contacts(Contacts { contacts: s })
764    }
765}
766
767impl Contactsish for AddressList {
768    fn len(&self) -> usize {
769        match self {
770            Self::Contacts(c) => c.len(),
771            Self::Group(g) => g.contacts.len(),
772        }
773    }
774
775    fn is_empty(&self) -> bool {
776        match self {
777            Self::Contacts(c) => c.is_empty(),
778            Self::Group(g) => g.contacts.is_empty(),
779        }
780    }
781
782    fn to_contacts(self) -> Contacts {
783        match self {
784            Self::Contacts(c) => c,
785            Self::Group(g) => g.contacts,
786        }
787    }
788
789    fn add<C>(&mut self, contact: C)
790    where
791        C: Contactish,
792    {
793        match self {
794            Self::Contacts(c) => c.add(contact),
795            Self::Group(g) => g.add(contact),
796        }
797    }
798
799    fn contains(&self, contact: &Contact) -> bool {
800        match self {
801            Self::Contacts(c) => c.contains(contact),
802            Self::Group(g) => g.contains(contact),
803        }
804    }
805}
806
807impl From<Contacts> for AddressList {
808    fn from(contacts: Contacts) -> Self {
809        Self::Contacts(contacts)
810    }
811}
812
813impl From<Group> for AddressList {
814    fn from(group: Group) -> AddressList {
815        Self::Group(group)
816    }
817}
818
819#[cfg(feature = "mailparse-conversions")]
820impl TryInto<Vec<mailparse::MailAddr>> for AddressList {
821    type Error = Error;
822
823    fn try_into(self) -> Result<Vec<mailparse::MailAddr>, Error> {
824        match self {
825            Self::Group(g) => Ok(vec![g.try_into()?]),
826            Self::Contacts(c) => c.into_iter().map(|ic| ic.try_into()).collect(),
827        }
828    }
829}