基於 Kotlin 特性實現的驗證框架

一. kvalidation 介紹

kvalidation 地址:https://github.com/fengzhizi715/kvalidation

它包含如下的功能:

  • DSL 風格
  • 支持對象的驗證
  • 內含多個驗證規則,也支持自定義驗證規則
  • 支持對象中屬性的驗證
  • 支持 RxJava

二. kvalidation 設計

2.1 類的驗證

首先,定義一個 ValidateRule 的範型接口並使用逆變,它表示類的驗證規則。

可以查看之前的文章:《Kotlin 範型之協變、逆變》 瞭解逆變。

ValidateRule 包含了兩個方法:

interface ValidateRule<in T> {

    fun validate(data: T): Boolean

    fun errorMessage(): String
}

然後,定義一個用於驗證類的 Validator,它繼承自 LinkedHashSet。可以將 ValidateRule 通過 addRule() 添加到 Validator,另外 addRule() 還是使用了infix修飾。

真正的類的驗證是在 validate() 進行的,當所有的 ValidateRule 都通過時,纔算真正的驗證通過。任何一個 ValidateRule 驗證失敗,都會導致類的驗證失敗。

open class Validator<T> : LinkedHashSet<ValidateRule<T>>() {

    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<T>): Validator<T> {
        add(rule)
        return this
    }
}

2.2 屬性的驗證

屬性的驗證是通過 PropertyValidator 類實現的,和之前的 Validator 無關。PropertyValidator 的主要方法包括 mustBe()、field()、fields()。

類的屬性通過 field() 方法進行判斷,多個屬性可以通過 fields() 方法進行判斷,而 mustBe() 方法可以不限定任何屬性。

class PropertyValidator<T> (

    private val validationProcessItems: MutableList<ValidationProcessItem<T>> = mutableListOf(),
    private val fieldNames: List<String> = emptyList()) {

    fun mustBe(specName: String = "", validateFunction: T.() -> Boolean): ValidationSpec<T> {

        val spec = ValidationSpec(specName = specName, validateFunction = validateFunction, fieldNames = fieldNames)
        validationProcessItems.add(spec)
        return spec
    }

    fun field(fieldName: String, block: PropertyValidator<T>.() -> Unit) {
        val fieldValidator = PropertyValidator(validationProcessItems, listOf(fieldName))
        block.invoke(fieldValidator)
    }

    fun fields(vararg fieldNames: String, block: PropertyValidator<T>.() -> Unit) {
        val fieldValidator = PropertyValidator(validationProcessItems, fieldNames.toList())
        block.invoke(fieldValidator)
    }

    ......
}

類的屬性也有類似的 ValidateRule。它是 ValidationSpec ,用於驗證屬性的某一條規則。

open class ValidationSpec<T>(specName: String = "",
                             fieldNames: List<String>,
                             val validateFunction: T.() -> Boolean) : ValidationProcessItem<T>(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 <T> defineValidator(block: Validator<T>.() -> Unit): Validator<T> {
    val v = Validator<T>()
    block.invoke(v)
    return v
}

因此,定義一個 Validator 很簡單,可以在 block 中添加 ValidateRule。

    val validator = defineValidator<String>{

        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<String>{

        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<T>(private val data: T) : Validator<T>() {

    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 <T> defineRxValidator(data: T, block: RxValidator<T>.() -> Unit): RxValidator<T> {
    val v = RxValidator<T>(data)
    block.invoke(v)
    return v
}

fun <T> T.rxValidator(block: RxValidator<T>.() -> Unit): RxValidator<T> {
    val v = RxValidator<T>(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 <T> definePropertyValidator(block: PropertyValidator<T>.() -> Unit): PropertyValidator<T> {
    val v = PropertyValidator<T>()
    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<User> {

    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> T.validate(block: Validator<T>.() -> Unit): Boolean {
    val v = Validator<T>()
    block.invoke(v)
    return v.validate(this)
}

它實際上是調用了類的驗證,並添加了 EmailRule。

四. 總結

kvalidation 是一個基於 Kotlin 特性的驗證框架,這些特性包括範型、DSL、擴展函數、帶接收者的函數類型等等。因此,它使用起來簡潔,也有具有很好的可讀性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章