java - validator - La validación de beans no funciona con kotlin(JSR 380)
spring boot bean validation (2)
Responder
Parece ser un problema con Kotlin en este momento. Consulte youtrack.jetbrains.com/issue/KT-27049 para más información.
Solución
Rafal G. ya señaló que podríamos usar un validador personalizado como solución alternativa. Así que aquí hay un código:
La anotación:
import javax.validation.Constraint
import javax.validation.Payload
import kotlin.annotation.AnnotationTarget.*
import kotlin.reflect.KClass
@MustBeDocumented
@Constraint(validatedBy = [NoNullElementsValidator::class])
@Target(allowedTargets = [FUNCTION, FIELD, ANNOTATION_CLASS, CONSTRUCTOR, VALUE_PARAMETER, TYPE_PARAMETER])
@Retention(AnnotationRetention.RUNTIME)
annotation class NoNullElements(
val message: String = "must not contain null elements",
val groups: Array<KClass<out Any>> = [],
val payload: Array<KClass<out Payload>> = []
)
El validador validador:
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
class NoNullElementsValidator : ConstraintValidator<NoNullElements, Collection<Any>> {
override fun isValid(value: Collection<Any>?, context: ConstraintValidatorContext): Boolean {
// null values are valid
if (value == null) {
return true
}
return value.stream().noneMatch { it == null }
}
}
Y finalmente la clase de usuario actualizada:
data class User(
@field:NotEmpty
@field:NoNullElements
var roles: MutableSet<Role> = HashSet()
)
Si bien la validación funciona ahora, la ConstrainViolation resultante es ligeramente diferente. Por ejemplo, elementType
y propertyPath
difieren, como puede ver a continuación.
Java:
Kotlin:
La fuente está disponible aquí: https://gitlab.com/darkatra/jsr380-kotlin-issue/tree/workaround
Gracias de nuevo por tu ayuda Rafal G.
así que, antes de nada, no podría pensar en un mejor título para esta pregunta, así que estoy abierto a cambios.
Estoy tratando de validar un bean utilizando el mecanismo de validación de beans (JSR-380) con arranque de resorte.
Así que tengo un controlador como este:
@Controller
@RequestMapping("/users")
class UserController {
@PostMapping
fun createUser(@Validated user: User, bindingResult: BindingResult): ModelAndView {
return ModelAndView("someview", "user", user)
}
}
siendo esta la clase User escrita en kotlin:
data class User(
@field:NotEmpty
var roles: MutableSet<@NotNull Role> = HashSet()
)
y esta siendo la prueba:
@Test
internal fun shouldNotCreateNewTestWithInvalidParams() {
mockMvc.perform(post("/users")
.param("roles", "invalid role"))
.andExpect(model().attributeHasFieldErrors("user", "roles[]"))
}
Los roles no válidos se asignan a nulo.
Como puede ver, quiero que los roles
contengan al menos un elemento sin que ninguno de ellos sea nulo. Sin embargo, al probar el código anterior, no se informan errores de enlace si los roles
contienen valores nulos. Sin embargo, informa de un error si el conjunto está vacío. Estaba pensando que esto podría ser un problema con la forma en que el código de Kotlin se compila, ya que el mismo código funciona bien cuando la clase User se escribe en java. Me gusta esto:
@Data // just lombok...
public class User {
@NotEmpty
private Set<@NotNull Role> roles = new HashSet<>();
}
Mismo controlador, misma prueba.
Luego de verificar el código de bytes, noté que la versión de kotlin no incluye la anotación anidada de @NotNull
(ver más abajo).
Java:
private Ljava/util/Set; roles
@Ljavax/validation/constraints/NotEmpty;()
@Ljavax/validation/constraints/NotNull;() : FIELD, 0;
@Ljavax/validation/constraints/NotEmpty;() : FIELD, null
Kotlin:
private Ljava/util/Set; roles
@Ljavax/validation/constraints/NotEmpty;()
@Lorg/jetbrains/annotations/NotNull;() // added because roles is not nullable in kotlin
Ahora la pregunta es ¿porque?
Aquí hay un proyecto de muestra en caso de que quieras probar algo.
Intenta agregar ?
Me gusta esto:
data class User(
@field:Valid
@field:NotEmpty
var roles: MutableSet<@NotNull Role?> = HashSet()
)
Entonces el compilador de kotlin debería darse cuenta de que los roles podrían ser null
, y podría honrar la validación, sé poco sobre JSR380, así que solo estoy adivinando.