проверка бобов не работает с котлин (JSR 380)

так что в первую очередь я не мог придумать лучшего названия для этого вопроса, поэтому я открыт для изменений.

Я пытаюсь проверить компонент, используя механизм проверки компонентов (JSR-380) с загрузкой весны.

Итак, я получил контроллер вроде этого:

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

с этим будет класс User, написанный в kotlin:

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

и это испытание:

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

Недействительные роли отображаются в нуль.

Как вы можете видеть, я хочу, чтобы roles содержали хотя бы один элемент, при этом ни один из элементов не был бы нулевым. Однако при тестировании вышеуказанного кода ошибки привязки не сообщаются, если roles содержат нулевые значения. Он сообщает об ошибке, если набор пуст, хотя. Я думал, что это может быть проблемой с тем, как компилируется код kotlin, поскольку тот же самый код работает очень хорошо, когда класс User написан в java. Как это:

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

Тот же контроллер, тот же тест.

После проверки байт-кода я заметил, что версия kotlin не включает вложенную аннотацию @NotNull (см. Ниже).

Джава:

private Ljava/util/Set; roles
@Ljavax/validation/constraints/NotEmpty;()
@Ljavax/validation/constraints/NotNull;() : FIELD, 0;
@Ljavax/validation/constraints/NotEmpty;() : FIELD, null

Котлин:

private Ljava/util/Set; roles
@Ljavax/validation/constraints/NotEmpty;()
@Lorg/jetbrains/annotations/NotNull;() // added because roles is not nullable in kotlin

Теперь вопрос в том, почему?

Вот пример проекта, если вы хотите попробовать что-то.

Ответ 1

Ответ

Кажется, сейчас проблема с котлином. Обратитесь к KT-27049 за дополнительной информацией.


Временное решение

Рафал Г. уже указал, что мы можем использовать специальный валидатор в качестве обходного пути. Итак, вот какой код:

Аннотация:

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

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

И, наконец, обновленный класс User:

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

В настоящее время уже выполняется валидация, получившаяся ConstrainViolation немного отличается. Например, elementType и propertyPath отличаются, как вы можете видеть ниже.

Джава:

The Java Version

Котлин:

The Kotlin Version

Источник доступен здесь: https://gitlab.com/darkatra/jsr380-kotlin-issue/tree/workaround

Еще раз спасибо за вашу помощь Rafal G.

Ответ 2

Попробуйте добавить ? как это:

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

Тогда компилятор kotlin должен понимать, что роли могут быть null, и это может означать проверку, я мало знаю о JSR380, поэтому я просто догадываюсь.