work validator valid not example does custom bean java spring-boot kotlin bean-validation jsr380

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.