Skip to content

Commit

Permalink
Merge pull request #12 from Q42/feature/email-validation
Browse files Browse the repository at this point in the history
E-mail validation
  • Loading branch information
mbernson authored Oct 12, 2023
2 parents fcd2c5d + 798f942 commit 30834b7
Show file tree
Hide file tree
Showing 4 changed files with 133 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";
47 changes: 47 additions & 0 deletions Sources/ValidationKit/Validators/Validator+Email.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Validator+Email.swift
// ValidationKit
//
// Created by Mathijs Bernson on 25/05/2023.
//

import Foundation

private let emailPattern = "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
"\\@" +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
"(" +
"\\." +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
")+"

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: emailPattern, 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"))
}
84 changes: 84 additions & 0 deletions Tests/ValidationKitTests/EmailTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import XCTest
import ValidationKit

class EmailTests: XCTestCase {
var validator: Validator<String, String>!

override func setUpWithError() throws {
validator = .email
}

func testValidEmailAddresses() throws {
XCTAssertTrue(validator.validate(input: "mathijsb+test@q42.nl").isValid)
XCTAssertTrue(validator.validate(input: "mathijsb@subdomain.q42.nl").isValid)

// Test cases from: https://www.softwaretestingo.com/test-cases-for-email-field/
XCTAssertTrue(validator.validate(input: "email@domain.com").isValid, "Valid email")
XCTAssertTrue(validator.validate(input: "firstname.lastname@domain.com").isValid, "The email contains a dot in the address field")
XCTAssertTrue(validator.validate(input: "email@subdomain.domain.com").isValid, "The email contains a dot with a subdomain")
XCTAssertTrue(validator.validate(input: "firstname+lastname@domain.com").isValid, "Plus sign is considered a valid character")
XCTAssertTrue(validator.validate(input: "email@123.123.123.123").isValid, "The domain is a valid IP address")
XCTAssertTrue(validator.validate(input: "1234567890@domain.com").isValid, "Digits in the address are valid")
XCTAssertTrue(validator.validate(input: "email@domain-one.com").isValid, "Dash in the domain name is valid")
XCTAssertTrue(validator.validate(input: "_______@domain.com").isValid, "Underscore in the address field is valid")
XCTAssertTrue(validator.validate(input: "email@domain.name").isValid, ".name is a valid Top Level Domain name")
XCTAssertTrue(validator.validate(input: "email@domain.co.jp").isValid, "Dot in Top Level Domain name also considered valid (use co.jp as an example here)")
XCTAssertTrue(validator.validate(input: "firstname-lastname@domain.com").isValid, "Dash in the address field is valid")

// Test cases from ChatGPT
XCTAssertTrue(validator.validate(input: "example@example.com").isValid, "Standard email format")
XCTAssertTrue(validator.validate(input: "john.doe@example.co.uk").isValid, "Email with a subdomain")
XCTAssertTrue(validator.validate(input: "user123@example123.com").isValid, "Email with numbers in the domain name")
XCTAssertTrue(validator.validate(input: "john_doe+test@example.com").isValid, "Email with special characters in the local part")
XCTAssertTrue(validator.validate(input: "user@example.io").isValid, "Email with a two-letter top-level domain (TLD)")
XCTAssertTrue(validator.validate(input: "test-email@example-domain.com").isValid, "Email with a hyphen in the domain name")
XCTAssertTrue(validator.validate(input: "a@example.com").isValid, "Email with a single-letter local part")
XCTAssertTrue(validator.validate(input: "thisisaverylongemailaddresswithlotsofcharacters@example.com").isValid, "Email with a long local part and domain name")
XCTAssertTrue(validator.validate(input: ".test@example.com").isValid, "Email with a dot at the beginning of the local part")
XCTAssertTrue(validator.validate(input: "test.@example.com").isValid, "Email with a dot at the end of the local part")

// These cases are currently not considered valid, but they should be
// 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")
}

func testInvalidEmailAddresses() throws {
XCTAssertFalse(validator.validate(input: "").isValid)
XCTAssertFalse(validator.validate(input: "foo").isValid)
XCTAssertFalse(validator.validate(input: "foobarbazquuxwhopper").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: "email.domain.com").isValid, "Missing @")
XCTAssertFalse(validator.validate(input: "email@domain").isValid, "Missing top-level domain (.com/.net/.org/etc.)")
XCTAssertFalse(validator.validate(input: "email@-domain.com").isValid, "The leading dash in front of the domain is invalid")
XCTAssertFalse(validator.validate(input: "email@domain..com").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: "john@.com").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@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: "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")

// These cases are currently considered valid, but they should not be
// XCTAssertFalse(validator.validate(input: "email.@domain.com").isValid, "Trailing dot in address is not allowed")
// XCTAssertFalse(validator.validate(input: "Joe Smith <email@domain.com>").isValid, "Encoded HTML within an email is invalid")
// XCTAssertFalse(validator.validate(input: "email@domain@domain.com").isValid, "Two @ sign")
// XCTAssertFalse(validator.validate(input: ".email@domain.com").isValid, "The leading dot in the address is not allowed")
// XCTAssertFalse(validator.validate(input: "email..email@domain.com").isValid, "Multiple dots")
// XCTAssertFalse(validator.validate(input: "あいうえお@domain.com").isValid, "Unicode char as address")
// XCTAssertFalse(validator.validate(input: "email@domain.com (Joe Smith)").isValid, "Text followed email is not allowed")
// XCTAssertFalse(validator.validate(input: "email@domain.web").isValid, ".web is not a valid top-level domain")
// XCTAssertFalse(validator.validate(input: "email@111.222.333.44444").isValid, "Invalid IP format")

// These cases are currently considered valid, but they should not be (test cases from ChatGPT)
// XCTAssertFalse(validator.validate(input: "john@doe@example.com").isValid, "Email with multiple @ symbols")
// XCTAssertFalse(validator.validate(input: "john doe@example.com").isValid, "Email with a space character")
// XCTAssertFalse(validator.validate(input: "john..doe@example.com").isValid, "Email with consecutive dots in the local part")
}
}

0 comments on commit 30834b7

Please sign in to comment.