宣言的にルールを定義してバリデーションを構築するためのライブラリー。
このライブラリーは、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 {
//...
}
このバリデーターは、値がnil
または、コレクションの場合は空かどうかを検証します。
Presence
は、nil
または空の場合、検証に失敗します。Absence
はその逆で、nil
または空でなければ検証に失敗します。
Presence(of: name)
Presence(of: email)
Absence(of: cancellationDate)
このバリデーターは、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を指定すれば、confirmedPassword
のPresence
の検証を別途宣言する必要はありません。
このバリデーターは、値が指定された正規表現と一致するか検証します。
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)
このバリデーターは、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(of: articleStatus, in: [.published, .secret])
Exclusion(of: permission, in: [.reader, .editor])
コレクションだけでなく範囲の検証も可能です。
Inclusion(of: age, in: 16...)
Exclusion(of: age, in: ..<16)
このバリデーターは、String
を含むコレクションの数が指定した範囲内かどうかを検証します。
Count(of: interests, within: 3...)
Count(of: username, within: 1..<20)
Count(of: productCode, exact: 8)
このバリデーター自身は、何かを検証する機能は持っておらず、initに与えた検証を実行します。
initは3種類用意されています。
- Errorを投げられるクロージャー (
() throws -> Void
)を受け取り ValidatorBuilder
クロージャーを受け取る- Type-erases a validator
このバリデーターを使うことでValidator
protocolに準拠した新しいバリデーターを実装することなしに、既存のバリデーターを組み合わせて任意の新しいバリデーターの構築が可能です。
ほとんどのビルドインバリデーターにはallowsNil()
modifierとallowsEmpty()
modifierを付与することができます。
allowsNil
: 検証する値がOptional
でnil
の場合に検証をスキップする。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`
}
}
検証に失敗すると、ValidationError
またはValidationErrors
が投げられます。
これらのエラーはそれぞれ、失敗した検証が単一か複数かに対応しています。
例えば、Validate
を除いてバリデーターを独立して利用した場合は、検証失敗時に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
は、ValidationError
を要素に持つコレクションです。
ValidationError
のイテレーションのほかに、指定したerror keyに該当するValidationError
の配列や失敗理由を取り出すことができます。
user.validationErrors?.reasons(for: \User.name)