Skip to content

Latest commit

 

History

History
310 lines (231 loc) · 10.2 KB

README_jp.md

File metadata and controls

310 lines (231 loc) · 10.2 KB

Swift Validations

宣言的にルールを定義してバリデーションを構築するためのライブラリー。

Overview

このライブラリーは、Ruby on RailsのActive Recordにインスパイアされたいくつかのバリデーターを用意しています。これらのバリデーターは、汎用的なバリデーションルールを提供します。
もし、独自のバリデーターが必要になれば、Validator protocolに準拠することでカスタムバリデーターを作成することが可能です。
これらのバリデーターの検証が失敗した際には、失敗理由やどこで失敗したかを表すエラーが投げられます。

ビルトインバリデーターや独自に実装したバリデーターは、宣言的な記述でモデルなどの型に組み込み、検証機能を提供することができます。

import Validations
import RegexBuilder

struct User: Validator {
    var name: String
    var age: UInt
    var email: String?
    var bio: String?
    var address: Address // Conforming Validator protocol.
    var password: String
    var confirmedPassword: String

    var validation: some Validator {
        Presence(of: name)
        Comparison(of: age, .greaterThan(16))

        Format(of: email) {
            ZeroOrMore {
                OneOrMore(.word)
                "."
            }
            OneOrMore(.word)
            "@"
            OneOrMore(.word)
            OneOrMore {
                "."
                OneOrMore(.word)
            }
        }
        .allowsNil()

        Presence(of: bio)
            .allowsNil()
            .allowsEmpty()

        address

        Presence(of: password)
            .allowsEmpty()

        Confirmation(of: confirmedPassword, matching: password)
    }
}

let user = User(...)
do {
    try user.validate()
} catch {
    //...
}

また、各バリデーターは個別に独立して利用することもできます。

import Validations

do {
    try Count(of: interests, within: 3...).validate()
} catch {
    //...
}

Built-in Validators

Presence / Absence

このバリデーターは、値がnilまたは、コレクションの場合は空かどうかを検証します。
Presenceは、nilまたは空の場合、検証に失敗します。Absenceはその逆で、nilまたは空でなければ検証に失敗します。

Presence(of: name)
Presence(of: email)
Absence(of: cancellationDate)

Confirmation

このバリデーターは、2つの値が完全一致するか検証します。メールアドレスやパスワードの確認フィールドの値が一致するかを検証することを想定しています。

Confirmation(of: confirmedPassword, matching: password)

この例では、confirmedPasswordの型がOptional<String>とします。この値が空文字列またはnilの場合、デフォルトでは検証はスキップされます。
これらの値を検証の失敗として扱いたい場合は、presence() modifierに.requiredを指定します。

Confirmation(of: confirmedPassword, matching: password)
    .presence(.required)

.requiredには、空文字列の場合のみまたは、nilの場合のみスキップする、といったオプションを与えることもできます。

Confirmation(of: confirmedPassword, matching: password)
    .presence(.required(allowsNil: true))
    
// or

Confirmation(of: confirmedPassword, matching: password)
    .presence(.required(allowsEmpty: true))

Note

Active Modelをご存知の方は、検証の主体がpassword側ではなく、confirmedPassword側であることにご注意ください。
presence(.required) modifierを指定すれば、confirmedPasswordPresenceの検証を別途宣言する必要はありません。

Format

このバリデーターは、値が指定された正規表現と一致するか検証します。

Format(of: productCode, with: #/\A[a-zA-Z\d]+\z/#)
// or
Format(of: productCode, with: /\A[a-zA-Z\d]+\z/)

RegexBuilderにも対応しています。

import RegexBuilder

Format(of: productCode) {
    Anchor.startOfSubject
    OneOrMore {
        CharacterClass(
            ("a"..."z"),
            ("A"..."Z"),
            .digit
        )
    }
    Anchor.endOfSubject
}

let predefinedRegex = Regex {
    Anchor.startOfSubject
    OneOrMore {
        CharacterClass(
            ("a"..."z"),
            ("A"..."Z"),
            .digit
        )
    }
    Anchor.endOfSubject
}
Format(of: productCode, with: predefinedRegex)

Comparison

このバリデーターは、2つの値の比較を検証します。

Comparison(of: startDate, .lessThanOrEqualTo(endDate))

第二引数に指定する値は、比較演算子と対応するものが用意されています。

検証する値は、基本的にComparableに準拠する必要があります。
ただし、Stringを除くArrayなどのコレクションはComparableには準拠していませんが、要素がComparableに準拠していれば検証することができます。
この検証には、lexicographicallyPrecedes(_:)が用いられます。

let currentVersion = [5, 10, 0]
let requiredVersion = [6, 0, 0]
Comparison(of: currentVersion, .greaterThanOrEqualTo(requiredVersion)).isValid // => false

Inclusion / Exclusion

このバリデーターは、値の包含関係を検証します。
Inclusionでは指定した値が含まれていなければ検証に失敗します。反対にExclusionは、指定した値が含まれていれば検証に失敗します。

Inclusion(of: articleStatus, in: [.published, .secret])
Exclusion(of: permission, in: [.reader, .editor])

コレクションだけでなく範囲の検証も可能です。

Inclusion(of: age, in: 16...)
Exclusion(of: age, in: ..<16)

Count

このバリデーターは、Stringを含むコレクションの数が指定した範囲内かどうかを検証します。

Count(of: interests, within: 3...)
Count(of: username, within: 1..<20)
Count(of: productCode, exact: 8)

Validate

このバリデーター自身は、何かを検証する機能は持っておらず、initに与えた検証を実行します。
initは3種類用意されています。

  • Errorを投げられるクロージャー (() throws -> Void)を受け取り
  • ValidatorBuilderクロージャーを受け取る
  • Type-erases a validator

このバリデーターを使うことでValidator protocolに準拠した新しいバリデーターを実装することなしに、既存のバリデーターを組み合わせて任意の新しいバリデーターの構築が可能です。

Presence Modifiers

ほとんどのビルドインバリデーターにはallowsNil() modifierとallowsEmpty() modifierを付与することができます。

  • allowsNil: 検証する値がOptionalnilの場合に検証をスキップする。
  • allowsEmpty: 検証する値がコレクションで要素が空の場合に検証をスキップする。

引数としてBoolを取るので、特定条件下では検証をスキップする、といった挙動にすることもできます。

Comparison(of: age, .greaterThan(16))
    .allowsNil(!isLoggedIn)

Validator protocolに準拠した独自のカスタムバリデーターをPresenceValidatable protocolに準拠させることで同様の機能を提供可能になります。

struct CustomValidator<Value>: Validator, PresenceValidatable {
    var value: Value?
    var presenceOption: PresenceOption = .required

    func validate() throws {
        guard let presenceValue = try validatePresence() else {
            return
        }

        // Validate with `presenceValue`
    }
}

Validation Errors

検証に失敗すると、ValidationErrorまたはValidationErrorsが投げられます。
これらのエラーはそれぞれ、失敗した検証が単一か複数かに対応しています。
例えば、Validateを除いてバリデーターを独立して利用した場合は、検証失敗時にValidationErrorが投げられます。

ValidationError

ValidationErrorは、検証に失敗したバリデーターとその理由に関する情報を持っています。
検証に失敗したバリデーターの情報はデフォルトでは保持していないため、失敗したバリデーターを特定したい場合はerror keyを設定する必要があります。

struct User: Validator {
    var name: String
    // ...

    var validation: some Validator {
        Presence(of: name)
            .errorKey(\Self.name)
        // ...
    }
}

この例では、error keyとしてKeyPathを設定していますが、他にもStringなどHashableに準拠した型を設定することができます。

Note

現行バージョンでは、失敗した場所を特定したければ、error keyを各バリデーターに明示的に与える必要があります。
このerror keyは、将来のバージョンで自動で付与されるような実装を検討中です。

また、複数のバリデーターに同じerror keyをまとめて付与することもできます。
この利用方法は、モデルなどに組み込む際にも有用です。

Validate {
    Presence(of: username)
    Count(of: bio, within: 0...1000)
}
.errorKey("Profile")

ValidationErrors

ValidationErrorsは、ValidationErrorを要素に持つコレクションです。
ValidationErrorのイテレーションのほかに、指定したerror keyに該当するValidationErrorの配列や失敗理由を取り出すことができます。

user.validationErrors?.reasons(for: \User.name)