未验证数据,已验证数据和验证的模式

时间:2019-11-05 08:58:53

标签: kotlin design-patterns

在我的Ktor REST API中,我收到JSON。 kotlinx.serialization为我方便地将其反序列化为简单数据类。在继续使用此数据之前,我必须进行一些验证。然后,我可以将其传递给代码的其他部分。

我想在反序列化但未经验证的数据,验证本身和经过验证的代码之间清楚地分开。

例如,我具有以下作用:

@Serializable
data class Input(val something: Int)

fun Input.validate() {
    if(something < 5) { throw WhateverException("invalid") }
}

但这有一些问题:当在任何地方接收到Input的实例时,我不能确定它已经被验证,因此为了安全起见,我会再次调用validate()

因此,为了避免这种情况,我想让validate()返回某个版本的Input,该版本告诉我数据是有效的,以便在我的代码库中可以使用方法签名来接受经过验证的数据

我知道我可以将类Input复制到称为ValidatedInput的私有类中,并让validate()返回该类,但这看起来像代码重复。 (我最终将拥有诸如Input之类的数十个类。)这也将阻止接口指定Input具有validate方法,并使其返回类似{{1 }}

我如何设计我的类和方法来清楚地表达这种分离,而无需重复代码?

2 个答案:

答案 0 :(得分:1)

如果要确保Input受到验证,则必须以某种方式将其放入类型系统中。由于Validated<Input>对您来说是一事无成(我更喜欢这样做),也许您可​​以通过向Input提供某种类型的验证令牌作为类型参数来解决这个问题?

例如这样的例子:

data class Input<out Validation>(val something: Int)

sealed class Validation
object Validated : Validation()
object NotValidated : Validation()

fun Input<NotValidated>.validate(): Input<Validated> {
    return if(something < 5) { throw RuntimeException("invalid") }
    else Input<Validated>(something)
}

以其他方式(Validated<Input>)进行操作在我看来更为灵活。它还不会将有关验证的任何代码混合到Input类中(就像验证令牌一样)。因此,例如,您可以执行以下操作:

sealed class Validated<T>
class Valid<T>(val value: T) : Validated<T>()
class Invalid<T>(val error: Exception): Validated<T>()

fun Input.validate(): Validated<Input> {
    return if(something < 5) Invalid(RuntimeException("invalid"))
    else Valid(this)
}

fun performAction(input: Valid<Input>) { 
    TODO("Do something with the valid Input")
}

fun main() {
    val validated = Input(5).validate()

    val result = when(validated) {
        is Valid -> performAction(validated)
        is Invalid -> throw validated.error
    }

    println(result)
}

答案 1 :(得分:0)

您可以将验证移至Input初始化:

@Serializable
data class Input(val something: Int) {
    init {
        validate()
    }
}

如果您这样做,那么所有Input实例都将有效。