Skip to content

Instantly share code, notes, and snippets.

@travisnewby
Last active October 12, 2020 15:12
Show Gist options
  • Save travisnewby/548e6dd20678c8efaa2cd1c8f8de4032 to your computer and use it in GitHub Desktop.
Save travisnewby/548e6dd20678c8efaa2cd1c8f8de4032 to your computer and use it in GitHub Desktop.
Validation Library
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