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.
Cela semble être un problème avec kotlin en ce moment. Reportez-vous à KT-27049 pour plus d'informations.
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:
Kotlin:
La source est disponible ici: https://gitlab.com/darkatra/jsr380-kotlin-issue/tree/workaround
Merci encore pour votre aide Rafal G.
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.