Coverage Report

Created: 2026-03-11 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/addr-0.15.6/src/email.rs
Line
Count
Source
1
//! Email address types
2
3
use crate::domain::Name;
4
use crate::error::{Kind, Result};
5
use crate::matcher;
6
#[cfg(feature = "net")]
7
#[cfg(not(feature = "std"))]
8
use crate::net::IpAddr;
9
use core::fmt;
10
#[cfg(not(any(feature = "net", feature = "std")))]
11
use core::str::FromStr;
12
use psl_types::List;
13
#[cfg(feature = "std")]
14
use std::net::IpAddr;
15
16
/// Holds information about a particular email address
17
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
18
pub struct Address<'a> {
19
    full: &'a str,
20
    at_sign: usize,
21
    host: Host<'a>,
22
}
23
24
impl<'a> Address<'a> {
25
0
    pub(crate) fn parse<T: List + ?Sized>(list: &T, address: &'a str) -> Result<Address<'a>> {
26
0
        if address.chars().count() > 254 {
27
0
            return Err(Kind::EmailTooLong);
28
0
        }
29
0
        let at_sign = address.rfind('@').ok_or(Kind::NoAtSign)?;
30
0
        let local = address.get(..at_sign).ok_or(Kind::NoUserPart)?;
31
0
        matcher::is_email_local(local)?;
32
0
        let rest = address.get(at_sign + 1..).ok_or(Kind::NoHostPart)?;
33
0
        let host = Host::parse(list, rest)?;
34
0
        Ok(Self {
35
0
            host,
36
0
            at_sign,
37
0
            full: address,
38
0
        })
39
0
    }
40
41
    /// The full email address as a `str`
42
0
    pub const fn as_str(&self) -> &'a str {
43
0
        self.full
44
0
    }
45
46
    /// The host part of the email address
47
0
    pub const fn host(&self) -> Host<'a> {
48
0
        self.host
49
0
    }
50
51
    /// The user (local) part of the email address
52
0
    pub fn user(&self) -> &'a str {
53
0
        &self.full[..self.at_sign]
54
0
    }
55
}
56
57
impl fmt::Display for Address<'_> {
58
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59
0
        write!(f, "{}", self.full)
60
0
    }
61
}
62
63
impl PartialEq<&str> for Address<'_> {
64
0
    fn eq(&self, other: &&str) -> bool {
65
0
        self.full == *other
66
0
    }
67
}
68
69
// A placeholder IP address that can never be constructed
70
#[cfg(not(any(feature = "net", feature = "std")))]
71
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
73
#[doc(hidden)]
74
pub enum IpAddr {}
75
76
#[cfg(not(any(feature = "net", feature = "std")))]
77
impl FromStr for IpAddr {
78
    type Err = Kind;
79
80
    fn from_str(_: &str) -> Result<Self> {
81
        unreachable!()
82
    }
83
}
84
85
/// Information about the host part of an email address
86
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
87
pub enum Host<'a> {
88
    Domain(Name<'a>),
89
    IpAddr(IpAddr),
90
}
91
92
impl<'a> Host<'a> {
93
0
    pub(crate) fn parse<T: List + ?Sized>(list: &T, host: &'a str) -> Result<Host<'a>> {
94
0
        if host.starts_with('[') && host.ends_with(']') {
95
0
            let host_len = host.len();
96
0
            if host_len < 3 {
97
0
                return Err(Kind::InvalidIpAddr);
98
0
            }
99
0
            if cfg!(not(any(feature = "net", feature = "std"))) {
100
0
                return Err(Kind::NetDisabled);
101
0
            }
102
0
            let ip_addr = host
103
0
                .get(1..host_len - 1)
104
0
                .ok_or(Kind::InvalidIpAddr)?
105
0
                .parse()?;
106
0
            Ok(Host::IpAddr(ip_addr))
107
        } else {
108
0
            Ok(Host::Domain(Name::parse(list, host)?))
109
        }
110
0
    }
111
}
112
113
#[cfg(test)]
114
mod test {
115
    use super::Address;
116
    use psl::List;
117
118
    #[test]
119
    fn parse() {
120
        // Valid email addresses
121
        Address::parse(&List, "johndoe@example.com").unwrap();
122
        Address::parse(&List, "john.doe@example.com").unwrap();
123
        Address::parse(&List, "john+doe@example.com").unwrap();
124
        Address::parse(&List, r#""john doe"@example.com"#).unwrap();
125
126
        // Invalid email addresses
127
        Address::parse(&List, "@example.com").unwrap_err();
128
        Address::parse(&List, r#""@example.com"#).unwrap_err();
129
        Address::parse(&List, " @example.com").unwrap_err();
130
    }
131
132
    #[test]
133
    fn user() {
134
        let email = Address::parse(&List, "johndoe@localhost").unwrap();
135
        assert_eq!(email.user(), "johndoe");
136
    }
137
}