Skip to content

Commit

Permalink
Fixed bug with passedValidation property (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdrelling committed Dec 17, 2020
1 parent 09d495b commit cc668fa
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 76 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

source 'https://rubygems.org'

gem 'bundler', '~> 2.1.4'
gem 'bundler', '~> 2.2.2'
gem 'cocoapods', '~> 1.10.0'
gem 'danger', '~> 8.2.1'
gem 'danger-swiftformat', '~> 0.7.0'
Expand Down
9 changes: 5 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,16 @@ GEM

PLATFORMS
ruby
x86_64-darwin-19

DEPENDENCIES
bundler (~> 2.1.4)
bundler (~> 2.2.2)
cocoapods (~> 1.10.0)
danger (~> 8.2.0)
danger (~> 8.2.1)
danger-swiftformat (~> 0.7.0)
danger-swiftlint (~> 0.24.4)
danger-swiftlint (~> 0.24.5)
httparty (~> 0.18.1)
plist (~> 3.5.0)

BUNDLED WITH
2.1.4
2.2.2
18 changes: 4 additions & 14 deletions Sources/SpotHeroEmailValidator/SHValidationResult.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
// Copyright © 2019 SpotHero, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright © 2020 SpotHero, Inc. All rights reserved.

import Foundation

public class SHValidationResult: NSObject {
/// Indicates whether or not the email address being analyzed is in valid syntax and format.
@objc public let passedValidation: Bool

/// The autocorrect suggestion to be applied. Nil if there no suggestion.
@objc public let autocorrectSuggestion: String?

@objc public init(passedValidation: Bool, autocorrectSuggestion: String?) {
Expand Down
63 changes: 27 additions & 36 deletions Sources/SpotHeroEmailValidator/SpotHeroEmailValidator.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
// Copyright © 2019 SpotHero, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright © 2020 SpotHero, Inc. All rights reserved.

import Foundation

Expand All @@ -26,7 +13,7 @@ public class SpotHeroEmailValidator: NSObject {
private let commonDomains: [String]
private let ianaRegisteredTLDs: [String]

private override init() {
override private init() {
var dataDictionary: NSDictionary?

// All TLDs registered with IANA as of October 8th, 2019 at 11:28 AM CST (latest list at: http://data.iana.org/TLD/tlds-alpha-by-domain.txt)
Expand All @@ -39,36 +26,38 @@ public class SpotHeroEmailValidator: NSObject {
self.ianaRegisteredTLDs = dataDictionary?["IANARegisteredTLDs"] as? [String] ?? []
}

// TODO: Remove @objc when entirely converted into Swift
@objc public func validateAndAutocorrect(emailAddress: String) throws -> SHValidationResult {
let autocorrectSuggestion = self.autocorrectSuggestion(for: emailAddress)

return SHValidationResult(passedValidation: autocorrectSuggestion?.isEmpty == false,
autocorrectSuggestion: autocorrectSuggestion)
public func validateAndAutocorrect(emailAddress: String) throws -> SHValidationResult {
do {
// Attempt to get an autocorrect suggestion
// As long as no error is thrown, we can consider the email address to have passed validation
let autocorrectSuggestion = try self.autocorrectSuggestion(for: emailAddress)

return SHValidationResult(passedValidation: true,
autocorrectSuggestion: autocorrectSuggestion)
} catch {
return SHValidationResult(passedValidation: false, autocorrectSuggestion: nil)
}
}

// TODO: Remove @objc when entirely converted into Swift
@objc public func autocorrectSuggestion(for emailAddress: String) -> String? {
guard let validated = try? self.validateSyntax(of: emailAddress), validated else {
// throw Error.invalidSyntax
return nil
}
public func autocorrectSuggestion(for emailAddress: String) throws -> String? {
// Attempt to validate the syntax of the email address
// If the email address has incorrect format or syntax, an error will be thrown
try self.validateSyntax(of: emailAddress)

guard let emailParts = try? self.splitEmailAddress(emailAddress) else {
return nil
}
// Split the email address into its component parts
let emailParts = try self.splitEmailAddress(emailAddress)

var suggestedTLD = emailParts.tld

if !self.ianaRegisteredTLDs.contains(emailParts.tld),
let closestTLD = self.closestString(for: emailParts.tld, fromArray: self.commonTLDs, withTolerance: 0.5) {
let closestTLD = self.closestString(for: emailParts.tld, fromArray: self.commonTLDs, withTolerance: 0.5) {
suggestedTLD = closestTLD
}

var suggestedDomain = "\(emailParts.hostname).\(suggestedTLD)"

if !self.commonDomains.contains(suggestedDomain),
let closestDomain = self.closestString(for: suggestedDomain, fromArray: self.commonDomains, withTolerance: 0.25) {
let closestDomain = self.closestString(for: suggestedDomain, fromArray: self.commonDomains, withTolerance: 0.25) {
suggestedDomain = closestDomain
}

Expand All @@ -81,6 +70,7 @@ public class SpotHeroEmailValidator: NSObject {
return suggestedEmailAddress
}

@discardableResult
public func validateSyntax(of emailAddress: String) throws -> Bool {
// Split the email address into parts
let emailParts = try self.splitEmailAddress(emailAddress)
Expand Down Expand Up @@ -112,14 +102,14 @@ public class SpotHeroEmailValidator: NSObject {
return nil
}

var closestString: String? = nil
var closestString: String?
var closestDistance = Int.max

// TODO: Use better name for arrayString parameter
for arrayString in array {
let distance = Int(string.levenshteinDistance(from: arrayString))

if distance < closestDistance && Float(distance) / Float(string.count) < tolerance {
if distance < closestDistance, Float(distance) / Float(string.count) < tolerance {
closestDistance = distance
closestString = arrayString
}
Expand Down Expand Up @@ -158,8 +148,6 @@ public class SpotHeroEmailValidator: NSObject {

return (username, domain, tld)
}


}

// MARK: - Extensions
Expand Down Expand Up @@ -194,7 +182,10 @@ private extension String {
/// - [What characters are allowed in an email address? (Stack Overflow](https://stackoverflow.com/questions/2049502/what-characters-are-allowed-in-an-email-address)
private static let emailRegexPattern = "\(Self.emailUsernameRegexPattern)@\(Self.emailDomainRegexPattern)"

// swiftlint:disable:next line_length
private static let emailUsernameRegexPattern = #"(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")"#

// swiftlint:disable:next line_length
private static let emailDomainRegexPattern = #"(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"#

func isValidEmail() -> Bool {
Expand Down
54 changes: 33 additions & 21 deletions Tests/SpotHeroEmailValidatorTests/SpotHeroEmailValidatorTests.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
// Copyright © 2019 SpotHero, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright © 2020 SpotHero, Inc. All rights reserved.

@testable import SpotHeroEmailValidator
import XCTest
Expand Down Expand Up @@ -55,27 +42,52 @@ class SpotHeroEmailValidatorTests: XCTestCase {
}
}
}

func testValidEmailAddressPassesValidation() throws {
let email = "test@spothero.com"

let validationResult = try SpotHeroEmailValidator.shared.validateAndAutocorrect(emailAddress: email)

XCTAssertTrue(validationResult.passedValidation)
XCTAssertNil(validationResult.autocorrectSuggestion)
}

func testInvalidEmailAddressPassesValidation() throws {
let email = "test@gamil.con"

let validationResult = try SpotHeroEmailValidator.shared.validateAndAutocorrect(emailAddress: email)

XCTAssertTrue(validationResult.passedValidation)
XCTAssertNotNil(validationResult.autocorrectSuggestion)
}

func testEmailSuggestions() {
func testEmailSuggestions() throws {
let tests = [
// Emails with NO Autocorrect Suggestions
ValidatorTestModel(emailAddress: "test@gmail.com", suggestion: nil),
ValidatorTestModel(emailAddress: "test@yahoo.co.uk", suggestion: nil),
ValidatorTestModel(emailAddress: "test@googlemail.com", suggestion: nil),

// Emails with Autocorrect Suggestions
ValidatorTestModel(emailAddress: "test@gamil.con", suggestion: "test@gmail.com"),
ValidatorTestModel(emailAddress: "test@yaho.com.uk", suggestion: "test@yahoo.co.uk"),
ValidatorTestModel(emailAddress: "test@yahooo.co.uk", suggestion: "test@yahoo.co.uk"),
ValidatorTestModel(emailAddress: "test@goglemail.coj", suggestion: "test@googlemail.com"),
ValidatorTestModel(emailAddress: "test@goglemail.com", suggestion: "test@googlemail.com"),

// Emails with invalid syntax
ValidatorTestModel(emailAddress: "blorp", error: .invalidSyntax),
]

let validator = SpotHeroEmailValidator.shared

for test in tests {
if let suggestion = test.suggestion {
XCTAssertEqual(validator.autocorrectSuggestion(for: test.emailAddress), suggestion, "Test failed for email address: \(test.emailAddress)")
} else {
XCTAssertNil(validator.autocorrectSuggestion(for: test.emailAddress), "Test failed for email address: \(test.emailAddress)")
do {
let autocorrectSuggestion = try SpotHeroEmailValidator.shared.autocorrectSuggestion(for: test.emailAddress)
XCTAssertEqual(autocorrectSuggestion, test.suggestion, "Test failed for email address: \(test.emailAddress)")
} catch let error as SpotHeroEmailValidator.Error {
// If the test fails with an error, make sure the error was expected
XCTAssertEqual(test.error, error)
} catch {
XCTFail("Unexpected error has occurred: \(error.localizedDescription)")
}
}
}
Expand Down

0 comments on commit cc668fa

Please sign in to comment.