一个灵活的状态验证解决方案。
从谓词创建简单的验证器
let validateEmail = Verify<String>.that({ $0.contains("@") }, otherwise: .myError)
你可以通过扩展轻松地在任何类型上创建验证器
extension Verify where Subject == Int {
public static func greaterThanZero(otherwise error: Error) -> Validator_<Subject> {
Verify<Int>.that({ $0 > 0}, otherwise: error)
}
}
extension Verify where Subject == String {
public static func minLength(_ value: Int, otherwise error: Error) -> Validator_<Subject> {
Verify.that({ (string: String) in string.count >= value }, otherwise: error)
}
}
创建这些扩展后,它们将像这样可用
Verify<String>.minLength(10, otherwise: .myError)
Verify<Int>.greaterThanZero(otherwise: .myOtherError)
Verify 有两种组合方式,一种是顺序组合,另一种是并行组合。
顺序组合
在顺序组合中,一次只运行一个验证器,并且最多累积一个错误,因为链中的下一个验证器只有在前一个验证器成功时才会应用。
let emailValidator = Verify<String>.inOrder {
Verify.that({ $0.contains("@")}, otherwise: invalidEmail)
Verify.minLength(5, otherwise: invalidEmail)
}
let input = "1"
emailValidator.errors(input).count == 1
请注意,即使输入 "1" 未通过两个验证,也只会累积一个错误。 这通常是期望的行为,因为我们希望一次验证一个条件。
也可以写成
let emailValidator = Verify<String>
.that({ $0.contains("@")}, otherwise: invalidEmail)
.andThat({ $0.count >= 5}, otherwise: invalidEmail)
let input = "1"
emailValidator.errors(input).count == 1
并行组合
在并行组合中,我们同时运行所有验证器并累积所有错误。
let emailValidator = Verify<String>.atOnce {
Verify.that({ $0.contains("@")}, otherwise: invalidEmail)
Verify.minLength(5, otherwise: invalidEmail)
}
let input = "1"
emailValidator.errors(input).count == 2
前一个示例将累积两个错误。
工厂方法
方法 | 签名 | 描述 |
---|---|---|
Verify.that | (Predicate<S>) -> Validator<S> |
使用谓词验证 |
Verify.at | (KeyPath<S, P>, Predicate<P>) -> Validator<S> |
验证由键路径聚焦的属性 |
Verify.error | (Error) -> Validator<S> |
始终以指定的错误失败 |
组合
方法 | 签名 | 累积错误 |
---|---|---|
andThen | (Validator<S>) -> Validator<S> |
否 |
andThat / thenCheck | (Predicate) -> Validator |
否 |
add | (Validator<S>) -> Validator<S> |
是 |
addCheck | (Predicate<S>) -> Validator<S> |
是 |
实用程序
方法 | 签名 | 描述 |
---|---|---|
ignore | (Predicate<S>) -> Validator<S> |
当提供的谓词为真时,绕过验证器 |
给定一个模型,例如 UserRegistration 结构体
struct UserRegistration {
let email: String
let password: String
let passwordConfirmation: String
}
我们可以使用键路径将验证应用于特定属性。
let invalidEmail = UserRegistrationError.invalidEmail
let invalidPassword = UserRegistrationError.invalidPassword
let emailValidator = Verify<String>.inOrder {
Verify.minLength(5, otherwise: invalidEmail)
Verify.that({ $0.contains("@")}, otherwise: invalidEmail)
}
let password = Verify<String>.inOrder {
Verify<String>.that({ $0.count > 5}, otherwise: invalidPassword)
Verify.containsSomeOf(CharacterSet.symbols, otherwise: invalidPassword)
}
let registrationValidator = Verify<UserRegistration>.atOnce {
Verify<UserRegistration>.at(\.email, validator: emailValidator)
Verify<UserRegistration>.at(\.password, validator: password)
Verify<UserRegistration>.that({ $0.password == $0.passwordConfirmation }, otherwise: UserRegistrationError.passwordsDontMatch)
}
let errors = registrationValidator.errors(UserRegistration(email: "", password: "19d", passwordConfirmation: "12d"))
运行验证器就像传入一个参数一样简单,因为它只是一个函数。 为了更简洁,提供了一个 verify
方法,这个方法很特别,因为它除了将参数转发给调用的验证器之外,还可以用于过滤错误列表并将其强制转换为特定的错误类型。 只需提供一个特定的类型参数。
通常,你会将你的错误类型建模为类似于
struct FormError<FieldType>: Error {
enum Reason {
case invalidFormat, required
}
let reason: Reason
let field: FieldType
}
enum LoginField {
case email, password
}
在这些情况下,能够按字段对错误进行分组是很方便的。
typealias LoginFormError = FormError<LoginField>
let validator = Verify<Int>.atOnce {
Verify<Int>.error(LoginFormError(reason: .invalidFormat, field: .email))
Verify<Int>.error(LoginFormError(reason: .required, field: .password))
}
let groupedErrors: [LoginField: [LoginFormError]] = validator.groupedErrors(0, by: { (error: LoginFormError) in error.field })
// Or even
let fieldErrors: [LoginField: [LoginFormError.Reason]] = groupedErrors.mapValues({ $0.map({ $0.reason })})