Last active
February 12, 2023 15:38
-
-
Save glowcap/26d8d771ded0303262dc1304e77fdf09 to your computer and use it in GitHub Desktop.
Swift 5.7 Email Validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// 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