[Swift] Validatorというライブラリを使ってValidationする

最近Swiftを書いているのですが、Validation周りってみなさんどうやっているんですかね？

小さなアプリケーションなら適当に自分でやれば良いような気もしますが、ある程度の規模なら自作するか、ライブラリを使うかになるんじゃないかと思います。

仕事ではSwift3を使用しているので、対応している[adamwaite/Validator](https://github.com/adamwaite/Validator)を試してみました。

ちなみに、他に有名なライブラリには以下があるようです。SwiftCopの方は追々試してみようかなー。

* [jpotts18/SwiftValidator](https://github.com/jpotts18/SwiftValidator)
  * Swift3対応がまだリリースされていない(masterブランチを向ければ利用可能 ※ 2017/1/20時点)
* [andresinaka/SwiftCop](https://github.com/andresinaka/SwiftCop)

### 機能

[READMEのFeatures](https://github.com/adamwaite/Validator/blob/master/README.md#features)に書いてあります。
一応、必須、同値、数値の比較、文字列長、パターンマッチ、包含などのValidationRuleが予め用意されています。

ValidatorはUIKitなどの要素を拡張しているのですが、Swift歴が浅い自分にとってはライブラリのコードを読んで勉強になることが多く、面白かったです。

## 使い方

### 単純にValidation結果を返す

一番単純な使い方は、以下のようにRuleのインスタンスを作成し、validateメソッドを呼び出すかたちです。
(Boolを返すだけなので、メッセージは無視しています)

```swift
let rule = ValidationRuleLength(min: 0, max: 3, error: ValidationError(message: "エラー"))
rule.validate(input: "1234") // false
```

ValidationRuleLengthのコンストラクタに渡しているValidationErrorは独自に作成した([Validatorのサンプルにもありますが](https://github.com/adamwaite/Validator/blob/master/Example/Example/ValidationErrorType.swift))構造体です。
エラーメッセージを持たせるようにしています。

```swift
import Foundation

struct ValidationError: Error {
    public let message: String
    public init(message errorMessage: String) {
        message = errorMessage
    }
}
```

### Validation結果により処理を分ける

Validatorは[Stringに対してもextensionしてValidationできるようになっている](https://github.com/adamwaite/Validator/blob/master/Validator/Validator/Validatable.swift#L90)ので、`"1234".validate(rule: rule)`といった呼び出しも可能です。
この場合は結果として[ValidationResult](https://github.com/adamwaite/Validator/blob/master/Validator/Validator/ValidationResult.swift)というenumが返却されるので、validなのかinvalidなのかで処理を分ければいいです。

```swift
let rule = ValidationRuleLength(min: 0, max: 3, error: ValidationError(message: "エラー"))

let result = "1234".validate(rule: rule)

switch result {
  case .valid: print("😀")
  case .invalid(let failures): print(failures.first?.message)
}
```

### ボタンが押されたタイミングでValidationする

ほとんど使い方は変わりません。
validだった際は"😎"を、invalidだった際はValidationErrorに詰め込んだエラーメッセージを、表示用のラベルに格納しています。

```swift
@IBOutlet weak var sample: UITextField!
@IBOutlet weak var sampleState: UILabel!
@IBAction func next(_ sender: UIButton) {
    let validationResult = sample.validate(rule: ValidationRuleLength(min: 0, max: 3, error: ValidationError(message: "💩")))
    switch validationResult {
    case .valid:
        sampleState.text = "😎"
    case .invalid(let errors):
        let error = errors.first as? ValidationError
        sampleState.text = error?.message ?? "😱"
    }
}
```

### 入力が変化したタイミングでValidationする

UITextFieldに対してValidationルールを追加し、入力が変更されたタイミングでのValidationを有効にします。validationHandlerにはValidation結果を受け取って表示用のラベルの値を更新する関数をクロージャとして渡します。

これで、UITextFieldの値が変更されたタイミングでリアルタイムにValidationが実施され、入力の状態("💩"か"😎")が切り替わるようになります。

```swift
@IBOutlet weak var sample: UITextField!
@IBOutlet weak var sampleState: UILabel!
let rule = ValidationRuleLength(min: 0, max: 3, error: ValidationError(message: "💩"))
override func viewDidLoad() {
    super.viewDidLoad()
    sample.validationRules = ValidationRuleSet()
    sample.validationRules?.add(rule: rule)
    // 入力が変更されたタイミングでのValidationを有効にする
    sample.validateOnInputChange(enabled: true)
    sample.validationHandler = { result in self.updateSampleValidationState(result: result) }
}

func updateSampleValidationState(result: ValidationResult) {
    switch result {
    case .valid:
        sampleState.text = "😎"
    case .invalid(let failures):
        sampleState.text = (failures.first as? ValidationError)?.message
    }
}

```

## まとめ

最初はどう使えば良いのか戸惑いましたが、ライブラリ自体があまり大きくないのですぐに動きを把握することができました。どうなんでしょう？ライブラリとしてはそんなに悪くないかな、といった感じです。

別途自作Ruleの作り方やObserverパターンを利用する場合なんかも書けたらと思います。