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

How to make simple form validator in Swift #328

Open
onmyway133 opened this issue Jun 25, 2019 · 0 comments
Open

How to make simple form validator in Swift #328

onmyway133 opened this issue Jun 25, 2019 · 0 comments

Comments

@onmyway133
Copy link
Owner

onmyway133 commented Jun 25, 2019

Sometimes we want to validate forms with many fields, for example name, phone, email, and with different rules. If validation fails, we show error message.

We can make simple Validator and Rule

class Validator {
    func validate(text: String, with rules: [Rule]) -> String? {
        return rules.compactMap({ $0.check(text) }).first
    }

    func validate(input: InputView, with rules: [Rule]) {
        guard let message = validate(text: input.textField.text ?? "", with: rules) else {
            input.messageLabel.isHidden = true
            return
        }

        input.messageLabel.isHidden = false
        input.messageLabel.text = message
    }
}

struct Rule {
    // Return nil if matches, error message otherwise
    let check: (String) -> String?

    static let notEmpty = Rule(check: {
        return $0.isEmpty ? "Must not be empty" : nil
    })

    static let validEmail = Rule(check: {
        let regex = #"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,64}"#

        let predicate = NSPredicate(format: "SELF MATCHES %@", regex)
        return predicate.evaluate(with: $0) ? nil : "Must have valid email"
    })

    static let countryCode = Rule(check: {
        let regex = #"^\+\d+.*"#

        let predicate = NSPredicate(format: "SELF MATCHES %@", regex)
        return predicate.evaluate(with: $0) ? nil : "Must have prefix country code"
    })
}

Then we can use very expressively

let validator = Validator()
validator.validate(input: inputView, with: [.notEmpty, .validEmail])

Then a few tests to make sure it works

class ValidatorTests: XCTestCase {
    let validator = Validator()

    func testEmpty() {
        XCTAssertNil(validator.validate(text: "a", with: [.notEmpty]))
        XCTAssertNotNil(validator.validate(text: "", with: [.notEmpty]))
    }

    func testEmail() {
        XCTAssertNil(validator.validate(text: "onmyway133@gmail.com", with: [.validEmail]))
        XCTAssertNotNil(validator.validate(text: "onmyway133", with: [.validEmail]))
        XCTAssertNotNil(validator.validate(text: "onmyway133.com", with: [.validEmail]))
    }

    func testCountryCode() {
        XCTAssertNil(validator.validate(text: "+47 11 222 333", with: [.countryCode]))
        XCTAssertNotNil(validator.validate(text: "11 222 333", with: [.countryCode]))
        XCTAssertNotNil(validator.validate(text: "47 11 222 333", with: [.countryCode]))
    }
}

allSatisfy

To check if all rules are ok, we can use reduce

func check(text: String, with rules: [Rule]) -> Bool {
    return rules.allSatisfy({ $0.check(text).isOk })
}

Or more concisely, use allSatisfy

func check(text: String, with rules: [Rule]) -> Bool {
    return rules.allSatisfy({ $0.check(text).isOk })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant