use std::{fmt, str::FromStr}; use email_address::{EmailAddress, Options}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Email(String); #[derive(Debug, Clone, PartialEq, Eq)] pub enum EmailError { Empty, InvalidUtf8, InvalidFormat, } impl Email { pub fn new(s: impl AsRef) -> Result { let s = s.as_ref().trim(); if s.is_empty() { return Err(EmailError::Empty); } // Strict "address only" validation (no "Name ") EmailAddress::parse_with_options(s, Options::default().without_display_text()) .map_err(|_| EmailError::InvalidFormat)?; Ok(Self(s.to_owned())) } pub fn as_str(&self) -> &str { &self.0 } pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } pub fn into_inner(self) -> String { self.0 } pub fn into_bytes(self) -> Vec { self.0.into_bytes() } pub fn from_bytes(bytes: &[u8]) -> Result { let s = std::str::from_utf8(bytes).map_err(|_| EmailError::InvalidUtf8)?; Self::new(s) } } /* ---- std traits ---- */ impl fmt::Display for Email { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } impl FromStr for Email { type Err = EmailError; fn from_str(s: &str) -> Result { Email::new(s) } } impl TryFrom for Email { type Error = EmailError; fn try_from(value: String) -> Result { Email::new(value) } } impl TryFrom<&str> for Email { type Error = EmailError; fn try_from(value: &str) -> Result { Email::new(value) } } impl TryFrom<&[u8]> for Email { type Error = EmailError; fn try_from(value: &[u8]) -> Result { Email::from_bytes(value) } } impl TryFrom> for Email { type Error = EmailError; fn try_from(value: Vec) -> Result { Email::from_bytes(&value) } } impl AsRef for Email { fn as_ref(&self) -> &str { self.as_str() } } impl AsRef<[u8]> for Email { fn as_ref(&self) -> &[u8] { self.as_bytes() } } impl From for Vec { fn from(value: Email) -> Self { value.into_bytes() } }