web-dev-qa-db-fra.com

la validation du bean ne fonctionne pas avec kotlin (JSR 380)

donc tout d'abord je ne pouvais pas penser à un meilleur titre pour cette question donc je suis ouvert aux changements.

J'essaie de valider un bean en utilisant le mécanisme de validation du bean (JSR-380) avec Spring Boot.

J'ai donc un contrôleur comme celui-ci:

@Controller
@RequestMapping("/users")
class UserController {
    @PostMapping
    fun createUser(@Validated user: User, bindingResult: BindingResult): ModelAndView {
        return ModelAndView("someview", "user", user)
    }
}

avec ceci étant la classe User écrite en kotlin:

data class User(
    @field:NotEmpty
    var roles: MutableSet<@NotNull Role> = HashSet()
)

et ceci étant le test:

@Test
internal fun shouldNotCreateNewTestWithInvalidParams() {
    mockMvc.perform(post("/users")
        .param("roles", "invalid role"))
        .andExpect(model().attributeHasFieldErrors("user",  "roles[]"))
}

Les rôles non valides sont mappés à null.

Comme vous pouvez le voir, je veux que roles contienne au moins un élément sans qu'aucun des éléments ne soit nul. Cependant, lors du test du code ci-dessus, aucune erreur de liaison n'est signalée si roles contient des valeurs nulles. Il signale cependant une erreur si l'ensemble est vide. Je pensais que cela pourrait être un problème avec la façon dont le code kotlin se compile car le même code fonctionne très bien lorsque la classe User est écrite en Java. Comme ça:

@Data // just lombok...
public class User {
    @NotEmpty
    private Set<@NotNull Role> roles = new HashSet<>();
}

Même contrôleur, même test.

Après avoir vérifié le bytecode, j'ai remarqué que la version kotlin n'inclut pas l'annotation imbriquée @NotNull (Voir ci-dessous).

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

Maintenant, la question est pourquoi?

Voici un exemple de projet au cas où vous voudriez essayer des trucs.

17
Tommy Schmidt

Répondre

Cela semble être un problème avec kotlin en ce moment. Reportez-vous à KT-27049 pour plus d'informations.


Workaround

Rafal G. a déjà souligné que nous pouvions utiliser un validateur personnalisé comme solution de contournement. Voici donc du code:

L'annotation:

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>> = []
)

Le ConstraintValidator:

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 }
    }
}

Et enfin la classe utilisateur mise à jour:

data class User(
    @field:NotEmpty
    @field:NoNullElements
    var roles: MutableSet<Role> = HashSet()
)

Bien que la validation fonctionne maintenant, la contrainte de contrainte résultante est légèrement différente. Par exemple, elementType et propertyPath diffèrent comme vous pouvez le voir ci-dessous.

Java:

The Java Version

Kotlin:

The Kotlin Version

La source est disponible ici: https://gitlab.com/darkatra/jsr380-kotlin-issue/tree/workaround

Merci encore pour votre aide Rafal G.

4
Tommy Schmidt

Essayez d'ajouter ? comme ça:

data class User(
    @field:Valid
    @field:NotEmpty
    var roles: MutableSet<@NotNull Role?> = HashSet()
)

Ensuite, le compilateur kotlin devrait réaliser que les rôles pourraient être null, et qu'il pourrait honorer la validation, je connais peu de choses sur JSR380, donc je ne fais que deviner.

2
StevieB