Skip to content

Instantly share code, notes, and snippets.

@glowcap
Last active February 12, 2023 15:38
Show Gist options
  • Select an option

  • Save glowcap/26d8d771ded0303262dc1304e77fdf09 to your computer and use it in GitHub Desktop.

Select an option

Save glowcap/26d8d771ded0303262dc1304e77fdf09 to your computer and use it in GitHub Desktop.
Swift 5.7 Email Validation
/// Validation based on RFC 5321, but omitting some
/// cases to conform with email-as-username authentication
/// add those cases as needed
import Foundation
extension String {
var validEmail: Bool {
if exceedsEmailLength() { return false }
if containsDoubleDots() { return false }
let emailRegex = /[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/
guard let result = try? emailRegex.wholeMatch(in: self)
else { return false }
return !result.isEmpty
}
/// Returns `true` if String exceeds email address character limit.
private func exceedsEmailLength() -> Bool {
let usernameMax = 64
let totalMaxLength = 256
guard self.count <= totalMaxLength else { return true }
let components = self.components(separatedBy: "@")
if let username = components.first {
return username.count > usernameMax
}
return false
}
/// Returns `true` if String contains `..`
private func containsDoubleDots() -> Bool {
let doubleDot = /[.]{2,}/
return self.contains(doubleDot)
}
}
/// unit tests
import XCTest
@testable import <YOUR_PROJECT>
final class EmailValidation: XCTestCase {
/// Tests email validation with numeric domains
/// - Note: Technically, this should pass, but not as a username
func test_emailValidation_withNumericAddress() {
let sut = "conan@[216.109.118.76]"
XCTAssertFalse(sut.validEmail, "sut should fail")
}
/// Tests email extension validation
func test_emailValidation_emailExtension() {
/// should succeed
let sutCom = "conanobrien@latenight.com"
let sutInfo = "conanobrien@latenight.info"
let sutNet = "conanobrien@latenight.net"
let sutOrg = "conanobrien@latenight.org"
let sutPizza = "sconanobrien@latenight.museum"
XCTAssert(sutCom.validEmail, ".com should pass")
XCTAssert(sutInfo.validEmail, ".info should pass")
XCTAssert(sutNet.validEmail, ".net should pass")
XCTAssert(sutOrg.validEmail, ".org should pass")
XCTAssert(sutPizza.validEmail, ".pizza should pass")
/// should fail
let sutNone = "conan.obrien@latenight."
let sut1Char = "conan.obrien@latenight.c"
let sutHyphen = "conan.obrien@latenight.piz-za"
XCTAssertFalse(sutNone.validEmail, ". should fail")
XCTAssertFalse(sut1Char.validEmail, ".c should fail")
XCTAssertFalse(sut1Char.validEmail, ".piz-za should fail")
}
/// Tests email validation with hyphen in address
func test_emailValidation_hyphenInAddress() {
/// should succeed
let sutDomainHyphen = "conan.obrien@late-night.com"
let sutUsernameHyphen = "conan-obrien@latenight.com"
XCTAssert(sutDomainHyphen.validEmail, "domain hyphen should pass")
XCTAssert(sutUsernameHyphen.validEmail, "username hyphen should pass")
/// should fail
let sutStartsWithHyphen = "conan.obrien@latenight.-com"
let sutMiddleHyphen = "conan.obrien@latenight.c-om"
let sutEndsWithHyphen = "conan.obrien@latenight.com-"
XCTAssertFalse(sutStartsWithHyphen.validEmail, ".-com should fail")
XCTAssertFalse(sutMiddleHyphen.validEmail, ".c-om should fail")
XCTAssertFalse(sutEndsWithHyphen.validEmail, ".com- should fail")
}
/// Tests email validation with trailing dot `.`
func test_emailValidation_withTrailingDot() {
let sut = "conan.obrien@latenight.com."
XCTAssertFalse(sut.validEmail, "trailing . should fail")
}
/// Tests email validation with double dots `..`
func test_emailValidation_doubleDot() {
let sutUsernameWithDoubleDot = "conan..obrien@latenight.com"
let sutDomainWithDoubleDot = "conan@late..night.com"
let sutExtensionWithLeadingDoubleDot = "conan@latenight..com"
let sutExtensionWithTrailingDoubleDot = "conan@latenight.com.."
XCTAssertFalse(
sutUsernameWithDoubleDot.validEmail,
"username with double dots should fail"
)
XCTAssertFalse(
sutDomainWithDoubleDot.validEmail,
"domain with double dots should fail"
)
XCTAssertFalse(
sutExtensionWithLeadingDoubleDot.validEmail,
"extension with leading double dots should fail"
)
XCTAssertFalse(
sutExtensionWithTrailingDoubleDot.validEmail,
"extension with trailing double dots should fail"
)
}
/// Tests email validation with more than one email address
func test_emailValidation_moreThanOneAddress() {
let sut = "conan.obrien@latenight.com.andy.ricter@latenight.com"
XCTAssertFalse(sut.validEmail, "more than one email should fail")
}
/// Tests email validation with comma in address
func test_emailValidation_withComma() {
let sutDomainWithComma = "conan@late,night.com"
let sutExtensionWithComma = "conan@latenight.o,rg"
let sutUsernameWithComma = "conan,obrien@latenight.org"
let sutCommaInsteadOfDot = "conan@latenight,org"
XCTAssertFalse(sutDomainWithComma.validEmail, "domain with comma should fail")
XCTAssertFalse(sutExtensionWithComma.validEmail, "extension with comma should fail")
XCTAssertFalse(sutUsernameWithComma.validEmail, "username with comma should fail")
XCTAssertFalse(sutCommaInsteadOfDot.validEmail, "comma instead of dot should fail")
}
/// Tests email validation with space in address
func test_emailValidation_withSpaces() {
let sutUsernameWithTrailingSpace = "conan @latenight.com"
let sutUsernameWithLeadingSpace = " conan@latenight.com"
let sutUsernameWithContentSpace = "conan obrien@latenight.com"
let sutDomainWithLeadingSpace = "conan@ latenight.com"
let sutDomainWithTrailingSpace = "conan@latenight .com"
let sutDomainWithContentSpace = "conan@late night.com"
let sutExtensionWithLeadingSpace = "conan@latenight. com"
let sutExtensionWithTrailingSpace = "conan@latenight.com "
let sutExtensionWithContenSpace = "conan@latenight.c om"
XCTAssertFalse(
sutUsernameWithTrailingSpace.validEmail,
"username with trailing space should fail"
)
XCTAssertFalse(
sutUsernameWithLeadingSpace.validEmail,
"username with leading space should fail"
)
XCTAssertFalse(
sutUsernameWithContentSpace.validEmail,
"username with contentspace should fail"
)
XCTAssertFalse(
sutDomainWithLeadingSpace.validEmail,
"domain with leading space should fail"
)
XCTAssertFalse(
sutDomainWithTrailingSpace.validEmail,
"domain with trailing space should fail"
)
XCTAssertFalse(
sutDomainWithContentSpace.validEmail,
"domain with content space should fail"
)
XCTAssertFalse(
sutExtensionWithLeadingSpace.validEmail,
"extension with leading space should fail"
)
XCTAssertFalse(
sutExtensionWithTrailingSpace.validEmail,
"extension with space trailing should fail"
)
XCTAssertFalse(
sutExtensionWithContenSpace.validEmail,
"extension with space content should fail"
)
}
/// Tests email validation with extreme lenths
func test_emailValidation_length() {
/// max username length 64
let username65Chars = Array(
repeating: "a",
count: 65
).joined()
/// max email length 256
let username257Chars = Array(
repeating: "b",
count: 257
).joined()
let overMaxLengthUsername = "\(username65Chars)@latenight.com"
let overMaxLengthEmail = "\(username257Chars)@latenight.com"
XCTAssertFalse(overMaxLengthUsername.validEmail, "Over 64 char length username should fail")
XCTAssertFalse(overMaxLengthEmail.validEmail, "Over 256 char length email should fail")
}
/// Tests email validation allowed characters ._%+- for userName
/// - Note: This does not contain all RFC 5321 valid special characters
func test_emailValidation_usernameWithAllowedSpecialChars() {
let sutUnderbar = "conan_@latenight.com"
let sutPercent = "conan%@latenight.com"
let sutPlus = "conan+@latenight.com"
let sutHyphen = "conan-@latenight.com"
XCTAssert(sutUnderbar.validEmail, "username with _ should pass")
XCTAssert(sutPercent.validEmail, "username with % should pass")
XCTAssert(sutPlus.validEmail, "username with + should pass")
XCTAssert(sutHyphen.validEmail, "username with - should pass")
}
/// Tests email validation disallowed characters !#$%&'*+-/=?^_`{|}~
func test_emailValidation_usernameWithDisallowedChars() {
let sutBang = "conan!@latenight.com"
let sutHash = "conan#@latenight.com"
let sutDollar = "conan$@latenight.com"
let sutAmpersand = "conan&@latenight.com"
let sutComma = "conan,@latenight.com"
let sutStar = "conan*@latenight.com"
let sutForwardSlash = "conan/@latenight.com"
let sutEqual = "conan=@latenight.com"
let sutQuestion = "conan?@latenight.com"
let sutCarot = "conan^@latenight.com"
let sutBacktick = "conan`@latenight.com"
let sutOpenBrace = "conan{@latenight.com"
let sutCloseBrace = "conan}@latenight.com"
let sutBar = "conan|@latenight.com"
let sutTilda = "conan~@latenight.com"
XCTAssertFalse(sutBang.validEmail, "username with ! should fail")
XCTAssertFalse(sutHash.validEmail, "username with # should fail")
XCTAssertFalse(sutDollar.validEmail, "username with $ should fail")
XCTAssertFalse(sutAmpersand.validEmail, "username with & should fail")
XCTAssertFalse(sutComma.validEmail, "username with , should fail")
XCTAssertFalse(sutStar.validEmail, "username with * should fail")
XCTAssertFalse(sutForwardSlash.validEmail, "username with / should fail")
XCTAssertFalse(sutEqual.validEmail, "username with = should fail")
XCTAssertFalse(sutQuestion.validEmail, "username with ? should fail")
XCTAssertFalse(sutCarot.validEmail, "username with ^ should fail")
XCTAssertFalse(sutBacktick.validEmail, "username with ` should fail")
XCTAssertFalse(sutOpenBrace.validEmail, "username with { should fail")
XCTAssertFalse(sutCloseBrace.validEmail, "username with } should fail")
XCTAssertFalse(sutBar.validEmail, "username with | should fail")
XCTAssertFalse(sutTilda.validEmail, "username with ~ should fail")
}
/// Tests email validation with an RCS-noncompliant character in domain
func test_emailValidation_domainWithInvalidCharacters() {
let sutDomainWithHexaDecimal = "conan.obrien@\\u015flatenight.com"
let sutDomainWithApostrophe = "conan@o'brien.latenight.com"
XCTAssertFalse(sutDomainWithHexaDecimal.validEmail, "address with hexadecimal should fail")
XCTAssertFalse(sutDomainWithApostrophe.validEmail, "domain with ' should fail")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment