Last active
October 12, 2020 15:12
-
-
Save travisnewby/548e6dd20678c8efaa2cd1c8f8de4032 to your computer and use it in GitHub Desktop.
Validation Library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import Combine | |
protocol Validator { | |
func validate(_ field: String) -> Validation.ValidationResult | |
} | |
enum Validation { | |
enum ValidationResult: Equatable { | |
case valid, invalid(ValidationError), empty | |
} | |
enum ValidationError: Equatable { | |
case tooShort(minimum: Int), tooLong(maximum: Int), wrongFormat, improper, inUse | |
} | |
struct Factory { | |
enum Kind { case email, password, name, username, confirmation, zip, existence } | |
static func validator(of kind: Kind) -> Validator { | |
switch kind { | |
case .email: | |
return Validators.EmailValidator() | |
case .password: | |
return Validators.PasswordValidator() | |
case .name: | |
return Validators.NameValidator() | |
case .username: | |
return Validators.UsernameValidator() | |
case .confirmation: | |
return Validators.ConfirmationValidator() | |
case .zip: | |
return Validators.ZipValidator() | |
case .existence: | |
return Validators.ExistenceValidator() | |
} | |
} | |
static func publisher(of kind: Kind, for string: Published<String>.Publisher) -> AnyPublisher<Validation.ValidationResult, Never> { | |
string.map { return validator(of: kind).validate($0) }.eraseToAnyPublisher() | |
} | |
} | |
} | |
extension Validation { | |
enum Validators { | |
struct EmailValidator: Validator { | |
func validate(_ field: String) -> ValidationResult { | |
guard field.count > 0 else { return .empty } | |
let emailPredicate = NSPredicate(format:"SELF MATCHES %@", Rules.emailRegex) | |
return emailPredicate.evaluate(with: field) ? .valid : .invalid(.wrongFormat) | |
} | |
} | |
struct PasswordValidator: Validator { | |
func validate(_ field: String) -> ValidationResult { | |
guard field.count > 0 else { return .empty } | |
return field.count >= Rules.passwordMinimumLength ? .valid : .invalid(.tooShort(minimum: Rules.passwordMinimumLength)) | |
} | |
} | |
struct NameValidator: Validator { | |
func validate(_ field: String) -> ValidationResult { | |
guard field.count > 0 else { return .empty } | |
return field.count >= Rules.nameMinimumLength ? .valid : .invalid(.tooShort(minimum: Rules.nameMinimumLength)) | |
} | |
} | |
struct UsernameValidator: Validator { | |
func validate(_ field: String) -> ValidationResult { | |
guard field.count > 0 else { return .empty } | |
if field.count < Rules.usernameMinimumLength { | |
return .invalid(.tooShort(minimum: Rules.usernameMinimumLength)) | |
} | |
if field.count > Rules.usernameMaximumLength { | |
return .invalid(.tooLong(maximum: Rules.usernameMaximumLength)) | |
} | |
return .valid | |
} | |
} | |
struct ConfirmationValidator: Validator { | |
func validate(_ field: String) -> ValidationResult { | |
guard field.count > 0 else { return .empty } | |
return field.count >= Rules.confirmationCodeMinimumLength ? .valid : .invalid(.tooShort(minimum: Rules.confirmationCodeMinimumLength)) | |
} | |
} | |
struct ZipValidator: Validator { | |
private static let zipLength = Rules.zipCodeLength | |
func validate(_ field: String) -> ValidationResult { | |
guard field.count > 0 else { return .empty } | |
guard let _ = Int(field) else { return .invalid(.wrongFormat) } | |
guard field.count > Self.zipLength - 1 else { return .invalid(.tooShort(minimum: Self.zipLength))} | |
return field.count < Self.zipLength + 1 ? .valid : .invalid(.tooLong(maximum: Self.zipLength)) | |
} | |
} | |
struct ExistenceValidator: Validator { | |
func validate(_ field: String) -> ValidationResult { | |
guard field.count > 0 else { return .empty } | |
return .valid | |
} | |
} | |
} | |
} | |
extension Validation { | |
enum Rules { | |
static let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" | |
static let zipCodeLength = 5 | |
static let passwordMinimumLength = 8 | |
static let passwordMaximumLength = 99 | |
static let passwordRules = UITextInputPasswordRules(descriptor: "required: lower; required: upper; required: digit; required: [-]; minlength: 16; maxlength: 24;") | |
static let usernameMinimumLength = 3 | |
static let usernameMaximumLength = 15 | |
static let nameMinimumLength = 1 | |
static let nameMaximumLength = 99 | |
static let confirmationCodeMinimumLength = 6 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment