一. kvalidation 介绍
kvalidation 地址:https://github.com/fengzhizi715/kvalidation
它包含如下的功能:
- DSL 风格
- 支持对象的验证
- 内含多个验证规则,也支持自定义验证规则
- 支持对象中属性的验证
- 支持 RxJava
二. kvalidation 设计
2.1 类的验证
首先,定义一个 ValidateRule 的范型接口并使用逆变,它表示类的验证规则。
可以查看之前的文章:《Kotlin 范型之协变、逆变》 了解逆变。
ValidateRule 包含了两个方法:
interface ValidateRule {
fun validate(data: T): Boolean
fun errorMessage(): String
}
然后,定义一个用于验证类的 Validator,它继承自 LinkedHashSet。可以将 ValidateRule 通过 addRule() 添加到 Validator,另外 addRule() 还是使用了infix
修饰。
真正的类的验证是在 validate() 进行的,当所有的 ValidateRule 都通过时,才算真正的验证通过。任何一个 ValidateRule 验证失败,都会导致类的验证失败。
open class Validator : LinkedHashSet>() {
fun validate(data: T,
onSuccess: (() -> Unit)? = null,
onError: ((String) -> Unit)? = null): Boolean {
forEach {
if (!it.validate(data)) {
onError?.invoke(it.errorMessage())
return false
}
}
onSuccess?.invoke()
return true
}
infix fun addRule(rule: ValidateRule): Validator {
add(rule)
return this
}
}
2.2 属性的验证
属性的验证是通过 PropertyValidator 类实现的,和之前的 Validator 无关。PropertyValidator 的主要方法包括 mustBe()、field()、fields()。
类的属性通过 field() 方法进行判断,多个属性可以通过 fields() 方法进行判断,而 mustBe() 方法可以不限定任何属性。
class PropertyValidator (
private val validationProcessItems: MutableList> = mutableListOf(),
private val fieldNames: List = emptyList()) {
fun mustBe(specName: String = "", validateFunction: T.() -> Boolean): ValidationSpec {
val spec = ValidationSpec(specName = specName, validateFunction = validateFunction, fieldNames = fieldNames)
validationProcessItems.add(spec)
return spec
}
fun field(fieldName: String, block: PropertyValidator.() -> Unit) {
val fieldValidator = PropertyValidator(validationProcessItems, listOf(fieldName))
block.invoke(fieldValidator)
}
fun fields(vararg fieldNames: String, block: PropertyValidator.() -> Unit) {
val fieldValidator = PropertyValidator(validationProcessItems, fieldNames.toList())
block.invoke(fieldValidator)
}
......
}
类的属性也有类似的 ValidateRule。它是 ValidationSpec ,用于验证属性的某一条规则。
open class ValidationSpec(specName: String = "",
fieldNames: List,
val validateFunction: T.() -> Boolean) : ValidationProcessItem(specName, fieldNames) {
private var messageFunction: ((T) -> String)? = null
fun errorMessage(messageFunction: T.() -> String) {
this.messageFunction = messageFunction
}
fun showMessage(target: T) = messageFunction?.invoke(target) ?: "validation failed"
fun isValid(target: T): Boolean = validateFunction(target)
}
ValidationSpec 的 isValid() 方法用于真正的验证,如果它返回 false 则表示该属性不满足当前的规则。
三. kvalidation 使用
3.1 使用 Validator
由于定义了一个 defineValidator()
fun defineValidator(block: Validator.() -> Unit): Validator {
val v = Validator()
block.invoke(v)
return v
}
因此,定义一个 Validator 很简单,可以在 block 中添加 ValidateRule。
val validator = defineValidator{
this addRule EmailRule()
}
val email = "[email protected]"
val result = validator.validate(email,onError = { println(it)})
println(result)
3.2 Validator 中添加多个校验规则
由于 Validator 是一个 LinkedHashSet,因此可以在 block 中添加多个 ValidateRule。
例如下面的密码校验,使用了两个 ValidateRule:
val validator = defineValidator{
this addRule MinLengthRule(6) // 密码长度不能小于6位
this addRule PatternRule("^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]+$") // 密码必须数字和字母的组合
}
val password = "123456a"
val result = validator.validate(password,onError = { println(it)})
println(result)
3.3 支持 RxJava 的使用
由于定义了一个 RxValidator
class RxValidator(private val data: T) : Validator() {
fun toObservable(success: (() -> Unit)? = null,error: ((String) -> Unit)? = null) =
Observable.just(data)
.map {
validate(it, onSuccess = { success?.invoke() }, onError = { message -> error?.invoke(message)
})
}
fun toFlowable(success: (() -> Unit)? = null,error: ((String) -> Unit)? = null) =
Flowable.just(data)
.map {
validate(it, onSuccess = { success?.invoke() }, onError = { message -> error?.invoke(message)
})
}
fun toSingle(success: (() -> Unit)? = null,error: ((String) -> Unit)? = null) =
Single.just(data)
.map {
validate(it, onSuccess = { success?.invoke() }, onError = { message -> error?.invoke(message)
})
}
fun toMaybe(success: (() -> Unit)? = null,error: ((String) -> Unit)? = null) =
Maybe.just(data)
.map {
validate(it, onSuccess = { success?.invoke() }, onError = { message -> error?.invoke(message)
})
}
}
并且定义了一个 defineRxValidator() 和扩展函数 rxValidator()
fun defineRxValidator(data: T, block: RxValidator.() -> Unit): RxValidator {
val v = RxValidator(data)
block.invoke(v)
return v
}
fun T.rxValidator(block: RxValidator.() -> Unit): RxValidator {
val v = RxValidator(this)
block.invoke(v)
return v
}
因此 RxJava 的结合使用变得很简单,下面分别使用两种方式展示了如何结合 RxJava 的使用:
val email = "[email protected]"
defineRxValidator(email){ this addRule EmailRule() }
.toObservable( error = { println(it)})
.subscribe{ println(it) }
val invalidEmail = "fengzhizi715@126"
invalidEmail.rxValidator { this addRule EmailRule() }
.toObservable( error = { println(it)})
.subscribe{ println(it) }
3.4 支持对象中属性的校验
参考上面的代码,在 kvalidation 中也事先定义了一个 definePropertyValidator()
fun definePropertyValidator(block: PropertyValidator.() -> Unit): PropertyValidator {
val v = PropertyValidator()
block.invoke(v)
return v
}
因此,在定义一个 PropertyValidator 时,也可以在 block 中添加多个 mustBe()、field()、fields() 方法。
在 field()、fields() 中,还可以添加多个 mustBe() 方法
data class User(val name: String = "tony",val password: String = "abcdefg", val confirmPassword: String = "abcdefg" ,val email:String = "abc#abc.com")
val propertyValidator = definePropertyValidator {
mustBe { name.isNotBlank() }
field("password") {
mustBe("password not blank") { password.isNotBlank() }
mustBe("password length range") { password.length in 6..20 }
}
fields("password", "confirmPassword") {
mustBe("password confirmPassword same") { password == confirmPassword }
}
field("email") {
mustBe("verify email") {
email.validate{
this addRule EmailRule()
}
}.errorMessage { "invalid email address" }
}
}
fun main() {
val user = User()
val result = propertyValidator.validateAll(user)
println(result)
println(propertyValidator.validate(user))
}
在 email 字段中,mustBe() 里使用了
email.validate{
this addRule EmailRule()
}
它是一个扩展函数:
fun T.validate(block: Validator.() -> Unit): Boolean {
val v = Validator()
block.invoke(v)
return v.validate(this)
}
它实际上是调用了类的验证,并添加了 EmailRule。
四. 总结
kvalidation 是一个基于 Kotlin 特性的验证框架,这些特性包括范型、DSL、扩展函数、带接收者的函数类型等等。因此,它使用起来简洁,也有具有很好的可读性。