Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E-mail validation #12

Merged
merged 3 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: "[email protected]").isValid)
XCTAssertTrue(validator.validate(input: "[email protected]").isValid)

// 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 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")

// 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 protected]").isValid, "The leading dash in front of the domain is invalid")
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@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 protected]").isValid, "Trailing dot in address is not allowed")
// XCTAssertFalse(validator.validate(input: "Joe Smith <[email protected]>").isValid, "Encoded HTML within an email is invalid")
// 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, "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 protected]").isValid, ".web is not a valid top-level domain")
// XCTAssertFalse(validator.validate(input: "[email protected]").isValid, "Invalid IP format")

// These cases are currently considered valid, but they should not be (test cases from ChatGPT)
// 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: "[email protected]").isValid, "Email with consecutive dots in the local part")
}
}