diff --git a/src/lib.rs b/src/lib.rs index aac7e46..611ed4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -354,7 +354,7 @@ impl Default for Options { /// create an instance. The various components of the email _are not_ parsed out to be accessible /// independently. /// -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Eq)] pub struct EmailAddress(String); // ------------------------------------------------------------------------------------------------ @@ -448,8 +448,8 @@ impl Display for EmailAddress { impl PartialEq for EmailAddress { fn eq(&self, other: &Self) -> bool { - let (left, right) = split_at(&self.0).unwrap(); - let (other_left, other_right) = split_at(&other.0).unwrap(); + let (left, right, _) = split_parts(&self.0).unwrap(); + let (other_left, other_right, _) = split_parts(&other.0).unwrap(); left.eq(other_left) && right.eq_ignore_ascii_case(other_right) } } @@ -645,10 +645,48 @@ impl EmailAddress { /// ``` /// pub fn local_part(&self) -> &str { - let (left, _) = split_at(&self.0).unwrap(); + let (left, _, _) = split_parts(&self.0).unwrap(); left } + /// + /// Returns the display part of the email address. This is borrowed so that no additional + /// allocation is required. + /// + /// ```rust + /// use email_address::*; + /// use std::str::FromStr; + /// + /// assert_eq!( + /// EmailAddress::from_str("Name ").unwrap().display_part(), + /// String::from("Name") + /// ); + /// ``` + /// + pub fn display_part(&self) -> &str { + let (_, _, display) = split_parts(&self.0).unwrap(); + display + } + + /// + /// Returns the email part of the email address. This is borrowed so that no additional + /// allocation is required. + /// + /// ```rust + /// use email_address::*; + /// use std::str::FromStr; + /// + /// assert_eq!( + /// EmailAddress::from_str("Name ").unwrap().email(), + /// String::from("name@example.org") + /// ); + /// ``` + /// + pub fn email(&self) -> String { + let (left, right, _) = split_parts(&self.0).unwrap(); + format!("{}@{}", left, right) + } + /// /// Returns the domain of the email address. This is borrowed so that no additional /// allocation is required. @@ -664,7 +702,7 @@ impl EmailAddress { /// ``` /// pub fn domain(&self) -> &str { - let (_, right) = split_at(&self.0).unwrap(); + let (_, right, _) = split_parts(&self.0).unwrap(); right } @@ -719,12 +757,31 @@ fn parse_address(address: &str, options: Options) -> Result // Deals with cases of '@' in `local-part`, if it is quoted they are legal, if // not then they'll return an `InvalidCharacter` error later. // - let (left, right) = split_at(address)?; - parse_local_part(left, options)?; - parse_domain(right, options)?; + let (local_part, domain, _) = split_parts(address)?; + parse_local_part(local_part)?; + parse_domain(domain)?; Ok(EmailAddress(address.to_owned())) } +fn split_parts(address: &str) -> Result<(&str, &str, &str), Error> { + let (display, email) = split_display_email(address)?; + let (local_part, domain) = split_at(email)?; + Ok((local_part, domain, display)) +} + +fn split_display_email(text: &str) -> Result<(&str, &str), Error> { + match text.rsplit_once(" <") { + None => Ok(("", text)), + Some((left, right)) => { + let right = right.trim(); + let email = &right[0..right.len() - 1]; + let display_name = left.trim(); + + Ok((display_name, email)) + } + } +} + fn split_at(address: &str) -> Result<(&str, &str), Error> { match address.rsplit_once(AT) { None => Error::MissingSeparator.into(), @@ -999,7 +1056,10 @@ mod tests { #[test] fn test_good_examples_from_wikipedia_09() { - is_valid("admin@mailserver1", Some("local domain name with no TLD, although ICANN highly discourages dotless email addresses")); + is_valid( + "admin@mailserver1", + Some("local domain name with no TLD, although ICANN highly discourages dotless email addresses"), + ); } #[test] @@ -1268,9 +1328,10 @@ mod tests { #[test] fn test_bad_examples_from_wikipedia_02() { - expect("a\"b(c)d,e:f;gi[j\\k]l@example.com", + expect( + "a\"b(c)d,e:f;gi[j\\k]l@example.com", Error::InvalidCharacter, - Some("none of the special characters in this local-part are allowed outside quotation marks") + Some("none of the special characters in this local-part are allowed outside quotation marks"), ); } @@ -1287,9 +1348,12 @@ mod tests { #[test] fn test_bad_examples_from_wikipedia_04() { - expect("this is\"not\\allowed@example.com", + expect( + "this is\"not\\allowed@example.com", Error::InvalidCharacter, - Some("spaces, quotes, and backslashes may only exist when within quoted strings and preceded by a backslash") + Some( + "spaces, quotes, and backslashes may only exist when within quoted strings and preceded by a backslash", + ), ); }