在我的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 }}
我如何设计我的类和方法来清楚地表达这种分离,而无需重复代码?
答案 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
实例都将有效。