Skip to content
Snippets Groups Projects
Validator.swift 4.49 KiB
Newer Older
Bruno Muniz's avatar
Bruno Muniz committed
import Shared
import Foundation

private enum Constants {
    static let codeMinimum = Localized.Validator.Code.minimum

    static let emailRegex = Localized.Validator.Email.regex
    static let emailInvalid = Localized.Validator.Email.invalid

    static let phoneMinimum = Localized.Validator.Phone.minimum
    static let phoneMaximum = Localized.Validator.Phone.maximum
    static let phoneRegexIssue = Localized.Validator.Phone.regexIssue

    static let usernameEmpty = Localized.Validator.Username.empty
    static let usernameRegex = Localized.Validator.Username.regex
    static let usernameMinimum = Localized.Validator.Username.minimum
    static let usernameMaximum = Localized.Validator.Username.maximum
    static let usernameInvalid = Localized.Validator.Username.invalid
    static let usernameApproved = Localized.Validator.Username.approved
    static let usernameStartEndInvalid = Localized.Validator.Username.startEnd
}

public enum ValidationResult {
    case success(String?)
    case failure(String)
}

public struct Validator<T> {
    public var validate: (T) -> ValidationResult
}

public extension Validator where T == (String, String) {
    static var phone: Self {
        Validator { regex, phone -> ValidationResult in
            guard phone.count >= 4 else {
                return .failure(Constants.phoneMinimum)
            }

            guard phone.count <= 30 else {
                return .failure(Constants.phoneMaximum)
            }

            let regularExpression = try? NSRegularExpression(pattern: regex)

            guard let regex = regularExpression, regex.firstMatch(in: phone, options: [], range: phone.fullRange()) != nil else {
                return .failure(Constants.phoneRegexIssue)
            }

            return .success(nil)
        }
    }
}

public extension Validator where T == String {
Ahmed Shehata's avatar
Ahmed Shehata committed
    static var backupPassphrase: Self {
        Validator { passphrase -> ValidationResult in
            guard passphrase.trimmingCharacters(in: .whitespacesAndNewlines).count >= 8 else {
                return .failure("")
            }

            let regex = try? NSRegularExpression(pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d!@#$%^&*]{8,}$")
Ahmed Shehata's avatar
Ahmed Shehata committed

            guard let regex = regex, regex.firstMatch(in: passphrase, options: [], range: passphrase.fullRange()) != nil else {
                return .failure("")
            }

            return .success(nil)
        }
    }

Bruno Muniz's avatar
Bruno Muniz committed
    static var username: Self {
        Validator { username -> ValidationResult in
            guard username.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false else {
                return .failure(Constants.usernameEmpty)
            }

            guard let first = username.first,
                  let last = username.last,
                  (first.isLetter || first.isNumber),
                  (last.isLetter || last.isNumber) else {
Ahmed Shehata's avatar
Ahmed Shehata committed
                return .failure(Constants.usernameStartEndInvalid)
            }
Bruno Muniz's avatar
Bruno Muniz committed

            guard username.trimmingCharacters(in: .whitespacesAndNewlines).count >= 4 else {
                return .failure(Constants.usernameMinimum)
            }

            guard username.trimmingCharacters(in: .whitespacesAndNewlines).count <= 32 else {
                return .failure(Constants.usernameMaximum)
            }

            let regex = try? NSRegularExpression(pattern: Constants.usernameRegex)

            guard let regex = regex, regex.firstMatch(in: username, options: [], range: username.fullRange()) != nil else {
                return .failure(Constants.usernameInvalid)
            }

            return .success(Constants.usernameApproved)
        }
    }

    static var email: Self {
        Validator { string -> ValidationResult in
            let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
            let matches = detector?.matches(in: string, options: [], range: string.fullRange())

            guard let match = matches?.first,
                  matches?.count == 1,
                  match.url?.scheme == "mailto",
                  match.range == string.fullRange() else { return .failure(Constants.emailInvalid) }

            return .success(nil)
        }
    }

    static var code: Self {
        Validator { code -> ValidationResult in
            guard code.count >= 4 else {
                return .failure(Constants.codeMinimum)
            }

            return .success(nil)
        }
    }
}

private extension String {
    func fullRange() -> NSRange {
        NSRange(self.startIndex..., in: self)
    }
}