Skip to content

Commit

Permalink
E-mail validation
Browse files Browse the repository at this point in the history
  • Loading branch information
mbernson committed May 25, 2023
1 parent 74edd01 commit 5375076
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
"invalid_date" = "is not a valid date";
"not_accepted" = "is not accepted";
"invalid_option" = "invalid option %@";
"invalid_email" = "invalid e-mail address";
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
"invalid_date" = "is geen geldige datum";
"not_accepted" = "is niet geaccepteerd";
"invalid_option" = "ongeldige optie %@";
"invalid_email" = "ongeldig e-mailadres";
40 changes: 40 additions & 0 deletions Sources/ValidationKit/Validators/Validator+Email.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Validator+Email.swift
// ValidationKit
//
// Created by Mathijs Bernson on 25/05/2023.
//

import Foundation

public extension Validator {
/// Validates whether the value is a valid e-mail address, according to the WHATWG HTML living standard.
///
/// A valid email address is a string that matches the email production of the following ABNF, the character set for which is Unicode. This ABNF implements the extensions described in RFC 1123.
///
/// ```
/// email = 1*( atext / "." ) "@" label *( "." label )
/// label = let-dig [ [ ldh-str ] let-dig ] ; limited to a length of 63 characters by RFC 1034 section 3.5
/// atext = < as defined in RFC 5322 section 3.2.3 >
/// let-dig = < as defined in RFC 1034 section 3.5 >
/// ldh-str = < as defined in RFC 1034 section 3.5 >
/// ```
///
/// Note: This requirement is a willful violation of RFC 5322, which defines a syntax for email addresses that is simultaneously too strict (before the "@" character), too vague (after the "@" character), and too lax (allowing comments, whitespace characters, and quoted strings in manners unfamiliar to most users) to be of practical use here.
///
/// Reference: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
static var email: Validator<String, String> {
Validator<String, String> { input in
if let range = input.range(of: #"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"#, options: .regularExpression) {
let output = String(input[range])
return .valid(output)
} else {
return .invalid(.invalidEmail)
}
}
}
}

public extension ValidationError {
static let invalidEmail = ValidationError(localizedDescription: NSLocalizedString("invalid_email", comment: "Invalid e-mail address error"))
}
71 changes: 71 additions & 0 deletions Tests/ValidationKitTests/EmailTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import XCTest
import ValidationKit

class EmailTests: XCTestCase {
func testValidEmailAddresses() {
let validator = Validator<String, String>.email

// Test cases from: https://www.softwaretestingo.com/test-cases-for-email-field/
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Valid email")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "The email contains a dot in the address field")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "The email contains a dot with a subdomain")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Plus sign is considered a valid character")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "The domain is a valid IP address")
XCTAssertTrue(validator.validate(input: "email@[123.123.123.123]").isValid, "A square bracket around the IP address is considered valid")
XCTAssertTrue(validator.validate(input: "“email”@domain.com").isValid, "Quotes around email are considered valid")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Digits in the address are valid")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Dash in the domain name is valid")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Underscore in the address field is valid")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, ".name is a valid Top Level Domain name")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Dot in Top Level Domain name also considered valid (use co.jp as an example here)")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Dash in the address field is valid")

// Test cases from ChatGPT
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Standard email format")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Email with a subdomain")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Email with numbers in the domain name")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Email with special characters in the local part")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Email with a two-letter top-level domain (TLD)")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Email with a hyphen in the domain name")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Email with a single-letter local part")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Email with a long local part and domain name")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Email with a dot at the beginning of the local part")
XCTAssertTrue(validator.validate(input: "[email protected]").isValid, "Email with a dot at the end of the local part")
}

func testInvalidEmailAddresses() {
let validator = Validator<String, String>.email

XCTAssertFalse(validator.validate(input: "@#@@##@%^%#$@#$@#.com").isValid)

// Test cases from: https://www.softwaretestingo.com/test-cases-for-email-field/
XCTAssertFalse(validator.validate(input: "plain address").isValid, "Missing @ sign and domain")
XCTAssertFalse(validator.validate(input: "#@%^%#$@#$@#.com").isValid, "Garbage")
XCTAssertFalse(validator.validate(input: "@domain.com").isValid, "Missing username")
XCTAssertFalse(validator.validate(input: "Joe Smith <[email protected]>").isValid, "Encoded HTML within an email is invalid")
XCTAssertFalse(validator.validate(input: "email.domain.com").isValid, "Missing @")
XCTAssertFalse(validator.validate(input: "email@[email protected]").isValid, "Two @ sign")
XCTAssertFalse(validator.validate(input: "[email protected]").isValid, "The leading dot in the address is not allowed")
XCTAssertFalse(validator.validate(input: "[email protected]").isValid, "Trailing dot in address is not allowed")
XCTAssertFalse(validator.validate(input: "[email protected]").isValid, "Multiple dots")
XCTAssertFalse(validator.validate(input: "あいうえお@domain.com").isValid, "Unicode char as address")
XCTAssertFalse(validator.validate(input: "[email protected] (Joe Smith)").isValid, "Text followed email is not allowed")
XCTAssertFalse(validator.validate(input: "email@domain").isValid, "Missing top-level domain (.com/.net/.org/etc.)")
XCTAssertFalse(validator.validate(input: "[email protected]").isValid, "The leading dash in front of the domain is invalid")
XCTAssertFalse(validator.validate(input: "[email protected]").isValid, ".web is not a valid top-level domain")
XCTAssertFalse(validator.validate(input: "[email protected]").isValid, "Invalid IP format")
XCTAssertFalse(validator.validate(input: "[email protected]").isValid, "Multiple dots in the domain portion is invalid")

// Test cases from ChatGPT
XCTAssertFalse(validator.validate(input: "example.com").isValid, "Missing @ symbol")
XCTAssertFalse(validator.validate(input: "[email protected]").isValid, "Email without a domain name")
XCTAssertFalse(validator.validate(input: "user@example").isValid, "Email without a top-level domain (TLD)")
XCTAssertFalse(validator.validate(input: "john@[email protected]").isValid, "Email with multiple @ symbols")
XCTAssertFalse(validator.validate(input: "john [email protected]").isValid, "Email with a space character")
XCTAssertFalse(validator.validate(input: "john@example#.com").isValid, "Email with invalid characters in the domain name")
XCTAssertFalse(validator.validate(input: "@example.com").isValid, "Email without a local part")
XCTAssertFalse(validator.validate(input: "[email protected]").isValid, "Email with consecutive dots in the local part")
XCTAssertFalse(validator.validate(input: "john_doe@_example.com").isValid, "Email with an underscore at the beginning of the domain name")
XCTAssertFalse(validator.validate(input: "user@example.").isValid, "Email with a missing domain extension")
}
}

0 comments on commit 5375076

Please sign in to comment.