Initial push

This commit is contained in:
Simon Johnston
2020-03-23 20:27:58 -07:00
commit db2e8bb60c
5 changed files with 768 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
# Common language header
language: rust
cache: cargo
dist: xenial
# Channels and versions I want to build
rust:
- stable
- beta
- 1.40.0
- nightly
# Operating systems I want to test
os:
- linux
- osx
- windows
# Set global environment only
env:
global:
- CARGO_CI=ci
- CARGO_DEBUG=1
- CARGO_DEPLOY=1
- CARGO_FLAGS=--verbose
- CARGO_LINTER=fmt
- secure: "SwxX4DVDM7eS3AjJhdDCAIWIYi9IMfiYIAu+n4Fx4fkPf8RwCg/h1gA4RV6WNZBpus8fzD065GHnYWP6BVllyFzoF2WYG9xBTcSA+gJHFrGSSqa7xNLEZCTUkSb0KLtVv7GbgVZgH4lHgh+BTfAYwTn/ty9bBoz+qlF9k0tRJsP7q8KsL+XSZWIQdQuuCFnjnkaNYoJPg3Iz4BDV77Wyr2hIK1bsos1NNslCUJxp3d3O16lEd0L2uU19iZ6ORgOaTq5NUHZ/mTaRFmtLgZjHHNp0YYaoZCZjNqG5PDXZm0vU7e7rprx6s9p1O9Ll6jEAQcE07tNa1Om26hsYCS4HP5+zqNrhb6d5oB6n5NjobOK3E1AamgbnJglh96lxqreOICGRHEwYurgq+IyxXFE+xGfkxsi45xcdiX0HueFxYgfjbjtB5osXhE0pci2UXMDwcNhjqPs3WZ1cCPQKjYx64Rxt1bnOHLj8BqNmk075wDtHNqzbNI5k5VK9IvajNfXnVLTghFqIccSHNOswVRAmcnH+nc83bj9XCe4JMOh0UzIngg+wRVIcvrTkxWWdVAE0m/8XAJr06ZWF2bGRK2AATJqSeonyBpYxl7kK/qJoUs8oCrcw0KIVe1/Wd7HUwLoy4zxuVxH07F0H/v231Zip3XMvz8P3/1Sfm27MuMYuZlw="
install:
- git clone https://github.com/johnstonskj/rust-ci.git $CARGO_CI
- $CARGO_CI/bin/cargo-lint.sh --install
jobs:
# Performance tweak
fast_finish: true
# Ignore failures in nightly, not ideal, but necessary
allow_failures:
- rust: nightly
- os: osx
# Script supports packages and workspaces
script:
- $CARGO_CI/bin/cargo-build.sh
- $CARGO_CI/bin/cargo-lint.sh
# Deployment script, this is under test
deploy:
provider: script
on:
tags: true
all_branches: true
condition: "$TRAVIS_RUST_VERSION = stable && $TRAVIS_OS_NAME = linux && $CARGO_DEPLOY = 1"
script: $CARGO_CI/bin/cargo-publish.sh
# Only initiate build on master or tag branches
branches:
only:
- master
- /\d+\.\d+(\.\d+)?(\-[a-z]+[a-zA-Z0-9]*)?/
# Suppress at least some emails
notifications:
email:
on_success: never
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "email_address"
version = "0.1.0"
authors = ["Simon Johnston <johnstonskj@gmail.com>"]
description = "Provides a new type for validation of email addresses"
edition = "2018"
license = "MIT"
readme = "README.md"
publish = true
[features]
default = ["serde_support"]
serde_support = ["serde"]
[dependencies]
# Optional dependencies
serde = { optional = true, version = "1.0", features = ["derive"] }
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Simon Johnston
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+56
View File
@@ -0,0 +1,56 @@
# Crate email_address
A Rust crate providing an implementation of an RFC-compliant `EmailAddress` newtype.
![MIT License](https://img.shields.io/badge/license-mit-118811.svg)
![Minimum Rust Version](https://img.shields.io/badge/Min%20Rust-1.40-green.svg)
[![crates.io](https://img.shields.io/crates/v/upnp-rs.svg)](https://crates.io/crates/email_address)
[![docs.rs](https://docs.rs/email_address/badge.svg)](https://docs.rs/email_address)
[![travis.ci](https://travis-ci.org/johnstonskj/rust-email_address.svg?branch=master)](https://travis-ci.org/johnstonskj/rust-email_address)
[![GitHub stars](https://img.shields.io/github/stars/johnstonskj/rust-email_address.svg)](https://github.com/johnstonskj/rust-email_address/stargazers)
TBD
## Example
```rust
use email_address::*;
assert!(EmailAddress::is_valid("user.name+tag+sorting@example.com"));
assert_eq!(
EmailAddress::from_str("Abc.example.com"),
Error::MissingSeparator.into()
);
```
## Specifications
1. RFC 3629: [_UTF-8, a transformation format of ISO 10646_](https://tools.ietf.org/html/rfc3629),
IETF, Nov 2003.
1. RFC 3696: [_Application Techniques for Checking and Transformation of
Names_](https://tools.ietf.org/html/rfc3696), IETF, Feb 2004.
1. RFC 5321: [_Simple Mail Transfer Protocol_](https://tools.ietf.org/html/rfc5321),
IETF, Oct 2008.
1. RFC 5322: [_Internet Message Format_](https://tools.ietf.org/html/rfc5322), I
ETF, Oct 2008.
1. RFC 5890: [_Internationalized Domain Names for Applications (IDNA): Definitions and Document
Framework_](https://tools.ietf.org/html/rfc5890), IETF, Aug 2010.
1. RFC 6531: [_SMTP Extension for Internationalized Email_](https://tools.ietf.org/html/rfc6531),
IETF, Feb 2012
1. RFC 5234: [_Augmented BNF for Syntax Specifications: ABNF_](https://tools.ietf.org/html/rfc5234),
IETF, Jan 2008.
## Changes
**Version 0.1.0**
* Basic type implementation and structure based on RFC 5322.
* Only supported for ASCII.
* Does not parse comments.
## TODO
1. Full Unicode support.
1. Support comments.
1. Support line-feed and whitespace rules.
+609
View File
@@ -0,0 +1,609 @@
/*!
A Rust crate providing an implementation of an RFC-compliant `EmailAddress` newtype.
# Example
```rust
use email_address::*;
assert!(EmailAddress::is_valid("user.name+tag+sorting@example.com"));
assert_eq!(
EmailAddress::from_str("Abc.example.com"),
Error::MissingSeparator.into()
);
```
# Specifications
1. RFC 3629: [_UTF-8, a transformation format of ISO 10646_](https://tools.ietf.org/html/rfc3629),
IETF, Nov 2003.
1. RFC 3696: [_Application Techniques for Checking and Transformation of
Names_](https://tools.ietf.org/html/rfc3696), IETF, Feb 2004.
1. RFC 5321: [_Simple Mail Transfer Protocol_](https://tools.ietf.org/html/rfc5321),
IETF, Oct 2008.
1. RFC 5322: [_Internet Message Format_](https://tools.ietf.org/html/rfc5322), I
ETF, Oct 2008.
1. RFC 5890: [_Internationalized Domain Names for Applications (IDNA): Definitions and Document
Framework_](https://tools.ietf.org/html/rfc5890), IETF, Aug 2010.
1. RFC 6531: [_SMTP Extension for Internationalized Email_](https://tools.ietf.org/html/rfc6531),
IETF, Feb 2012
1. RFC 5234: [_Augmented BNF for Syntax Specifications: ABNF_](https://tools.ietf.org/html/rfc5234),
IETF, Jan 2008.
From RFC 5322: §3.2.1. [Quoted characters](https://tools.ietf.org/html/rfc5322#section-3.2.1):
```ebnf
quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
```
From RFC 5322: §3.2.2. [Folding White Space and Comments](https://tools.ietf.org/html/rfc5322#section-3.2.2):
```ebnf
FWS = ([*WSP CRLF] 1*WSP) / obs-FWS
; Folding white space
ctext = %d33-39 / ; Printable US-ASCII
%d42-91 / ; characters not including
%d93-126 / ; "(", ")", or "\"
obs-ctext
ccontent = ctext / quoted-pair / comment
comment = "(" *([FWS] ccontent) [FWS] ")"
CFWS = (1*([FWS] comment) [FWS]) / FWS
```
From RFC 5322: §3.2.3. [Atom](https://tools.ietf.org/html/rfc5322#section-3.2.3):
```ebnf
atext = ALPHA / DIGIT / ; Printable US-ASCII
"!" / "#" / ; characters not including
"$" / "%" / ; specials. Used for atoms.
"&" / "'" /
"*" / "+" /
"-" / "/" /
"=" / "?" /
"^" / "_" /
"`" / "{" /
"|" / "}" /
"~"
atom = [CFWS] 1*atext [CFWS]
dot-atom-text = 1*atext *("." 1*atext)
dot-atom = [CFWS] dot-atom-text [CFWS]
specials = "(" / ")" / ; Special characters that do
"<" / ">" / ; not appear in atext
"[" / "]" /
":" / ";" /
"@" / "\" /
"," / "." /
DQUOTE
```
From RFC 5322: §3.2.4. [Quoted Strings](https://tools.ietf.org/html/rfc5322#section-3.2.4):
```ebnf
qtext = %d33 / ; Printable US-ASCII
%d35-91 / ; characters not including
%d93-126 / ; "\" or the quote character
obs-qtext
qcontent = qtext / quoted-pair
quoted-string = [CFWS]
DQUOTE *([FWS] qcontent) [FWS] DQUOTE
[CFWS]
```
From RFC 5322, §3.4.1. [Addr-Spec Specification](https://tools.ietf.org/html/rfc5322#section-3.4.1):
```ebnf
addr-spec = local-part "@" domain
local-part = dot-atom / quoted-string / obs-local-part
domain = dot-atom / domain-literal / obs-domain
domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
dtext = %d33-90 / ; Printable US-ASCII
%d94-126 / ; characters not including
obs-dtext ; "[", "]", or "\"
```
RFC 3696, §3. [Restrictions on email addresses](https://tools.ietf.org/html/rfc3696#section-3)
describes in detail the quoting of characters in an address.
RFC 6531, §3.3. [Extended Mailbox Address Syntax](https://tools.ietf.org/html/rfc6531#section-3.3)
extends the rules above for non-ASCII character sets.
```ebnf
UTF8-non-ascii = UTF8-2 / UTF8-3 / UTF8-4
```
This refers to RFC 6529 §4. [Syntax of UTF-8 Byte Sequences](https://tools.ietf.org/html/rfc3629#section-4):
> A UTF-8 string is a sequence of octets representing a sequence of UCS
> characters. An octet sequence is valid UTF-8 only if it matches the
> following syntax, which is derived from the rules for encoding UTF-8
> and is expressed in the ABNF of [RFC2234].
```ebnf
UTF8-octets = *( UTF8-char )
UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
UTF8-1 = %x00-7F
UTF8-2 = %xC2-DF UTF8-tail
UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
%xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
%xF4 %x80-8F 2( UTF8-tail )
UTF8-tail = %x80-BF
```
Comments in addresses are discussed in RFC 5322 Appendix A.5. [White Space, Comments, and Other
Oddities](https://tools.ietf.org/html/rfc5322#appendix-A.5).
An informal description can be found on [Wikipedia](https://en.wikipedia.org/wiki/Email_address).
# Example
*/
#![warn(
missing_debug_implementations,
missing_docs,
unused_extern_crates,
rust_2018_idioms
)]
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
// ------------------------------------------------------------------------------------------------
// Public Types
// ------------------------------------------------------------------------------------------------
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
InvalidCharacter,
LocalPartEmpty,
LocalPartTooLong,
DomainEmpty,
DomainTooLong,
MissingSeparator,
TooManySeparators,
DomainTooFew,
DomainInvalidSeparator,
InvalidIPAddress,
UnbalancedQuotes,
InvalidComment,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
pub struct EmailAddress(String);
// ------------------------------------------------------------------------------------------------
// Implementations
// ------------------------------------------------------------------------------------------------
const LOCAL_PART_MAX_LENGTH: usize = 64;
const DOMAIN_MAX_LENGTH: usize = 255;
const CR: char = '\r';
const LF: char = '\n';
const SP: char = ' ';
const HTAB: char = '\t';
const ESC: char = '\\';
const AT: char = '@';
const DOT: char = '.';
const DQUOTE: char = '"';
const LBRACKET: char = '[';
const RBRACKET: char = ']';
const LPAREN: char = '(';
const RPAREN: char = ')';
const LT: char = '<';
const GT: char = '>';
const IPV6_PREFIX: &str = "IPv6:";
const MAILTO_URI_PREFIX: &str = "mailto:";
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Error::InvalidCharacter => write!(f, "Invalid character."),
Error::LocalPartEmpty => write!(f, "Local part is empty."),
Error::LocalPartTooLong => write!(
f,
"Local part is too long. Length limit: {}",
LOCAL_PART_MAX_LENGTH
),
Error::DomainEmpty => write!(f, "Domain is empty."),
Error::DomainTooLong => {
write!(f, "Domain is too long. Length limit: {}", DOMAIN_MAX_LENGTH)
}
Error::MissingSeparator => write!(f, "Missing separator character '{}'.", AT),
Error::TooManySeparators => {
write!(f, "Found more than one separator character '{}'.", AT)
}
Error::DomainTooFew => write!(f, "Too few parts in the domain"),
Error::DomainInvalidSeparator => {
write!(f, "Invalid placement of the domain separator '{:?}", DOT)
}
Error::InvalidIPAddress => write!(f, "Invalid IP Address specified for doamin."),
Error::UnbalancedQuotes => write!(f, "Quotes around the local-part are unbalanced."),
Error::InvalidComment => write!(f, "A comment was badly formed."),
}
}
}
unsafe impl Send for Error {}
unsafe impl Sync for Error {}
impl std::error::Error for Error {}
impl<T> Into<std::result::Result<T, Error>> for Error {
fn into(self) -> Result<T, Error> {
Err(self)
}
}
// ------------------------------------------------------------------------------------------------
impl Display for EmailAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for EmailAddress {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_address(s)
}
}
impl EmailAddress {
pub fn is_valid(address: &str) -> bool {
Self::from_str(&address).is_ok()
}
pub fn from(local_part: &str, domain: &str) -> Result<Self, Error> {
Self::from_str(&format!("{}{}{}", local_part, AT, domain))
}
pub fn to_uri(&self) -> String {
format!("{}{}", MAILTO_URI_PREFIX, self)
}
pub fn to_display(&self, name: String) -> String {
format!("{} <{}>", name, self)
}
}
// ------------------------------------------------------------------------------------------------
// Private Functions
// ------------------------------------------------------------------------------------------------
fn parse_address(address: &str) -> Result<EmailAddress, Error> {
let address = if address.starts_with(LT) && address.ends_with(GT) {
&address[1..address.len() - 1]
} else {
address
};
let parts = address.split(AT).collect::<Vec<&str>>();
match parts.len() {
l if l < 2 => Error::MissingSeparator.into(),
l if l > 2 => Error::TooManySeparators.into(),
_ => {
parse_local_part(parts.first().unwrap())?;
parse_domain(parts.last().unwrap())?;
Ok(EmailAddress(address.to_string()))
}
}
}
fn parse_local_part(part: &str) -> Result<(), Error> {
if part.is_empty() {
Error::LocalPartEmpty.into()
} else if part.len() > LOCAL_PART_MAX_LENGTH {
Error::LocalPartTooLong.into()
} else if part.starts_with(DQUOTE) && part.ends_with(DQUOTE) {
parse_quoted_local_part(&part[1..part.len() - 1])
} else {
parse_unquoted_local_part(part)
}
}
fn parse_quoted_local_part(part: &str) -> Result<(), Error> {
if part.is_ascii() && is_qcontent(part) {
return Ok(());
}
Error::InvalidCharacter.into()
}
fn parse_unquoted_local_part(part: &str) -> Result<(), Error> {
if part.is_ascii() && is_dot_atom_text(part) {
return Ok(());
}
Error::InvalidCharacter.into()
}
fn parse_domain(part: &str) -> Result<(), Error> {
if part.is_empty() {
Error::DomainEmpty.into()
} else if part.len() > LOCAL_PART_MAX_LENGTH {
Error::DomainTooLong.into()
} else if part.starts_with(LBRACKET) && part.ends_with(RBRACKET) {
parse_literal_domain(&part[1..part.len() - 1])
} else {
parse_text_domain(part)
}
}
fn parse_text_domain(part: &str) -> Result<(), Error> {
if part.is_ascii() && is_dot_atom_text(part) {
return Ok(());
}
Error::InvalidCharacter.into()
}
fn parse_literal_domain(part: &str) -> Result<(), Error> {
if part.is_ascii() && part.chars().all(is_dtext_char) {
return Ok(());
}
Error::InvalidCharacter.into()
}
// ------------------------------------------------------------------------------------------------
fn is_atext(c: char) -> bool {
c.is_alphanumeric()
|| c == '!'
|| c == '#'
|| c == '$'
|| c == '%'
|| c == '&'
|| c == '\''
|| c == '*'
|| c == '+'
|| c == '-'
|| c == '/'
|| c == '='
|| c == '?'
|| c == '^'
|| c == '_'
|| c == '`'
|| c == '{'
|| c == '|'
|| c == '}'
|| c == '~'
}
fn is_atom(s: &str) -> bool {
!s.is_empty() && s.chars().all(is_atext)
}
fn is_dot_atom_text(s: &str) -> bool {
s.split(DOT).all(is_atom)
}
fn is_vchar(c: char) -> bool {
c >= '\x21' && c <= '\x7E'
}
fn is_wsp(c: char) -> bool {
c == SP || c == HTAB
}
fn is_ctext_char(c: char) -> bool {
(c >= '\x21' && c == '\x27') || (c >= '\x2A' && c <= '\x5B') || (c >= '\x5D' && c <= '\x7E')
}
fn is_qtext_char(c: char) -> bool {
c == '\x21' || (c >= '\x23' && c <= '\x5B') || (c >= '\x5D' && c <= '\x7E')
}
fn is_qcontent(s: &str) -> bool {
let mut char_iter = s.chars();
while let Some(c) = &char_iter.next() {
if c == &ESC {
// quoted-pair
match char_iter.next() {
Some(c2) if is_vchar(c2) => (),
_ => return false,
}
} else if !(is_wsp(*c) || is_qtext_char(*c)) {
// qtext
return false;
}
}
true
}
fn is_dtext_char(c: char) -> bool {
(c >= '\x21' && c <= '\x5A') || (c >= '\x5E' && c <= '\x7E')
}
fn is_ctext(s: &str) -> bool {
s.chars().all(is_ctext_char)
}
// ------------------------------------------------------------------------------------------------
// Unit Tests
// ------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_good_examples_from_wikipedia_01() {
assert!(EmailAddress::is_valid("simple@example.com"));
}
#[test]
fn test_good_examples_from_wikipedia_02() {
assert!(EmailAddress::is_valid("very.common@example.com"));
}
#[test]
fn test_good_examples_from_wikipedia_03() {
assert!(EmailAddress::is_valid(
"disposable.style.email.with+symbol@example.com"
));
}
#[test]
fn test_good_examples_from_wikipedia_04() {
assert!(EmailAddress::is_valid(
"other.email-with-hyphen@example.com"
));
}
#[test]
fn test_good_examples_from_wikipedia_05() {
assert!(EmailAddress::is_valid("fully-qualified-domain@example.com"));
}
#[test]
fn test_good_examples_from_wikipedia_06() {
// may go to user.name@example.com inbox depending on mail server
assert!(EmailAddress::is_valid("user.name+tag+sorting@example.com"));
}
#[test]
fn test_good_examples_from_wikipedia_07() {
// one-letter local-part
assert!(EmailAddress::is_valid("x@example.com"));
}
#[test]
fn test_good_examples_from_wikipedia_08() {
assert!(EmailAddress::is_valid("example-indeed@strange-example.com"));
}
#[test]
fn test_good_examples_from_wikipedia_09() {
// local domain name with no TLD, although ICANN highly discourages dotless email addresses
assert!(EmailAddress::is_valid("admin@mailserver1"));
}
#[test]
fn test_good_examples_from_wikipedia_10() {
// see the List of Internet top-level domains
assert!(EmailAddress::is_valid("example@s.example"));
}
#[test]
fn test_good_examples_from_wikipedia_11() {
// space between the quotes
assert!(EmailAddress::is_valid("\" \"@example.org"));
}
#[test]
fn test_good_examples_from_wikipedia_12() {
// quoted double dot
assert!(EmailAddress::is_valid("\"john..doe\"@example.org"));
}
#[test]
fn test_good_examples_from_wikipedia_13() {
// bangified host route used for uucp mailers
assert!(EmailAddress::is_valid("mailhost!username@example.org"));
}
#[test]
fn test_good_examples_from_wikipedia_14() {
// % escaped mail route to user@example.com via example.org
assert!(EmailAddress::is_valid("user%example.com@example.org"));
}
#[test]
fn test_good_examples_from_wikipedia_15() {
assert!(EmailAddress::is_valid("jsmith@[192.168.2.1]"));
}
#[test]
fn test_good_examples_from_wikipedia_16() {
assert!(EmailAddress::is_valid("jsmith@[IPv6:2001:db8::1]"));
}
#[test]
fn test_bad_examples_from_wikipedia_00() {
// (no @ character)
assert_eq!(
EmailAddress::from_str("Abc.example.com"),
Error::MissingSeparator.into()
);
}
#[test]
fn test_bad_examples_from_wikipedia_01() {
// (only one @ is allowed outside quotation marks)
assert_eq!(
EmailAddress::from_str("A@b@c@example.com"),
Error::TooManySeparators.into()
);
}
#[test]
fn test_bad_examples_from_wikipedia_02() {
// (none of the special characters in this local-part are allowed outside quotation marks)
assert_eq!(
EmailAddress::from_str("a\"b(c)d,e:f;g<h>i[j\\k]l@example.com"),
Error::InvalidCharacter.into()
);
}
#[test]
fn test_bad_examples_from_wikipedia_03() {
// (quoted strings must be dot separated or the only element making up the local-part)
assert_eq!(
EmailAddress::from_str("just\"not\"right@example.com"),
Error::InvalidCharacter.into()
);
}
#[test]
fn test_bad_examples_from_wikipedia_04() {
// (spaces, quotes, and backslashes may only exist when within quoted strings and preceded by a backslash)
assert_eq!(
EmailAddress::from_str("this is\"not\\allowed@example.com"),
Error::InvalidCharacter.into()
);
}
#[test]
fn test_bad_examples_from_wikipedia_05() {
// (even if escaped (preceded by a backslash), spaces, quotes, and backslashes must still be contained by quotes)
assert_eq!(
EmailAddress::from_str("this\\ still\"not\\allowed@example.com"),
Error::InvalidCharacter.into()
);
}
#[test]
fn test_bad_examples_from_wikipedia_06() {
// (local part is longer than 64 characters)
assert_eq!(
EmailAddress::from_str(
"1234567890123456789012345678901234567890123456789012345678901234+x@example.com"
),
Error::LocalPartTooLong.into()
);
}
}