diff --git a/src/lib.rs b/src/lib.rs index cf60d91..e19a838 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -345,11 +345,48 @@ pub struct Options { /// /// Sets the minimum number of domain segments that must exist to parse successfully. /// + /// ```rust + /// use email_address::*; + /// + /// assert!( + /// EmailAddress::parse_with_options( + /// "simon@localhost", + /// Options::default().with_no_minimum_sub_domains(), + /// ).is_ok() + /// ); + /// assert_eq!( + /// EmailAddress::parse_with_options( + /// "simon@localhost", + /// Options::default().with_required_tld() + /// ), + /// Err(Error::DomainTooFew) + /// ); + /// ``` + /// pub minimum_sub_domains: usize, /// /// Specifies if domain literals are allowed. Defaults to `true`. /// + /// ```rust + /// use email_address::*; + /// + /// assert!( + /// EmailAddress::parse_with_options( + /// "email@[127.0.0.256]", + /// Options::default().with_domain_literal() + /// ).is_ok() + /// ); + /// + /// assert_eq!( + /// EmailAddress::parse_with_options( + /// "email@[127.0.0.256]", + /// Options::default().without_domain_literal() + /// ), + /// Err(Error::UnsupportedDomainLiteral), + /// ); + /// ``` + /// pub allow_domain_literal: bool, /// @@ -494,6 +531,23 @@ impl Options { ..self } } + #[inline(always)] + /// Set the value of `minimum_sub_domains` to zero. + pub const fn with_no_minimum_sub_domains(self) -> Self { + Self { + minimum_sub_domains: 0, + ..self + } + } + #[inline(always)] + /// Set the value of `minimum_sub_domains` to two, this has the effect of requiring a + /// domain name with a top-level domain (TLD). + pub const fn with_required_tld(self) -> Self { + Self { + minimum_sub_domains: 2, + ..self + } + } /// Set the value of `allow_domain_literal` to `true`. #[inline(always)] pub const fn with_domain_literal(self) -> Self { @@ -963,11 +1017,13 @@ fn parse_text_domain(part: &str, options: Options) -> Result<(), Error> { if sub_part.is_empty() { return Error::SubDomainEmpty.into(); } + // As per https://www.rfc-editor.org/rfc/rfc1034#section-3.5, // the domain label needs to start with a `letter`; // however, https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address // specifies a label can start // with a `let-dig` (letter or digit), so we allow the wider range + if !sub_part.starts_with(char::is_alphanumeric) { return Error::InvalidCharacter.into(); } @@ -976,6 +1032,7 @@ fn parse_text_domain(part: &str, options: Options) -> Result<(), Error> { if !sub_part.ends_with(char::is_alphanumeric) { return Error::InvalidCharacter.into(); } + if sub_part.len() > SUB_DOMAIN_MAX_LENGTH { return Error::SubDomainTooLong.into(); } @@ -1816,6 +1873,24 @@ mod tests { ); } + #[test] + // Regression test: GitHub issue #23 + fn test_missing_tld() { + EmailAddress::parse_with_options("simon@localhost", Options::default()).unwrap(); + EmailAddress::parse_with_options( + "simon@localhost", + Options::default().with_no_minimum_sub_domains(), + ) + .unwrap(); + + expect_with_options( + "simon@localhost", + Options::default().with_required_tld(), + Error::DomainTooFew, + Some("too few domain segments"), + ); + } + #[test] // Regression test: GitHub issue #11 fn test_eq_name_case_sensitive_local() {