Параметры класса данных валидации Котлин
Если я моделирую свои объекты значений с использованием классов данных Kotlin, то какой метод лучше всего подходит для проверки. Кажется, что блок init является единственным логическим местом, поскольку он выполняется после основного конструктора.
data class EmailAddress(val address: String) {
init {
if (address.isEmpty() || !address.matches(Regex("^[a-zA-Z0-9][email protected][a-zA-Z0-9]+(.[a-zA-Z]{2,})$"))) {
throw IllegalArgumentException("${address} is not a valid email address")
}
}
}
Использование примера JSR-303
Недостатком этого является то, что требуется ткать время загрузки
@Configurable
data class EmailAddress(@Email val address: String) {
@Autowired
lateinit var validator: Validator
init {
validator.validate(this)
}
}
Ответы
Ответ 1
На самом деле, похоже, что валидация не несет ответственности за классы данных. data
говорит сам за себя - он используется для хранения данных.
-
Итак, если вы хотите проверить класс данных, будет иметь смысл установить проверку @get:
на аргументы конструктора и проверить вне класса данных в классе, ответственного за построение.
-
Второй вариант заключается не в том, чтобы использовать класс данных, просто использовать простой класс и реализовать целую логику в конструкторе, проходящем валидатор там
-
Кроме того, если вы используете Spring Framework - вы можете сделать этот класс Bean с областью прототипа, но, скорее всего, будет абсолютно неудобно работать с таким типом спагетти-кода:)
Ответ 2
Я сделал комментарий, но подумал, что вместо этого поделюсь своим подходом к валидации.
Во-первых, я считаю ошибкой выполнять проверку при создании экземпляра. Это сделает границу между десериализацией и передачей контроллерам грязной. Кроме того, для меня, если вы придерживаетесь чистой архитектуры, валидация является частью вашей основной логики, и вы должны убедиться, что тесты на вашей основной логике это происходит.
Итак, чтобы позволить мне заняться этим так, как я хочу, я сначала определю свой собственный основной API проверки. Чистый котлин. Нет рамок или библиотек. Держи в чистоте.
interface Validatable {
/**
* @throws [ValidationErrorException]
*/
fun validate()
}
class ValidationErrorException(
val errors: List<ValidationError>
) : Exception() {
/***
* Convenience method for getting a data object from the Exception.
*/
fun toValidationErrors() = ValidationErrors(errors)
}
/**
* Data object to represent the data of an Exception. Convenient for serialization.
*/
data class ValidationErrors(
val errors : List<ValidationError>
)
data class ValidationError(
val path: String,
val message: String
)
Тогда у меня есть конкретные рамки реализации. Например, реализация javax.validation.Validation
:
open class ValidatableJavax : Validatable {
companion object {
val validator = Validation.buildDefaultValidatorFactory().validator!!
}
override fun validate() {
val violations = validator.validate(this)
val errors = violations.map {
ValidationError(it.propertyPath.toString(), it.message)
}.toMutableList()
if (errors.isNotEmpty()) {
throw ValidationErrorException(errors = errors)
}
}
}
Единственная проблема в том, что javax-аннотации не очень хорошо работают с объектами данных kotlin - но вот пример класса с проверкой:
import javax.validation.constraints.Positive
class MyObject(
myNumber: BigDecimal
) : ValidatableJavax() {
@get:Positive(message = "Must be positive")
val myNumber: BigDecimal = myNumber
}