Валидация
Модуль валидации Kora проверяет модели, аргументы методов и результаты методов с помощью аннотаций.
Для моделей Kora генерирует Validator<T> во время компиляции, а для методов применяет аспект @Validate, который вызывает нужные проверки до или после выполнения метода.
Валидация работает без использования Reflection во время выполнения приложения: структура объекта, вложенные поля, сигнатуры методов и доступные валидаторы проверяются процессорами аннотаций во время сборки.
Ошибки валидации возвращаются в виде списка Violation либо выбрасываются как ViolationException.
Пошаговый разбор перед справочным описанием смотрите в разделе Валидация.
Подключение¶
Зависимость build.gradle:
Модуль:
Зависимость build.gradle.kts:
Модуль:
Модуль поставляет два интерфейса-примеси, и вы выбираете один в зависимости от того, обслуживает ли приложение HTTP:
| Модуль | Артефакт | Предоставляет | Когда использовать |
|---|---|---|---|
ValidatorModule |
validation-common |
Сгенерированные компоненты Validator<T>, все встроенные фабрики ограничений и валидаторы элементов (Validator<List<T>>, Validator<Set<T>>, Validator<Collection<T>>) |
Библиотеки и приложения без HTTP, либо когда вы обрабатываете ViolationException самостоятельно |
ValidationModule |
validation-module |
Всё из ValidatorModule плюс ValidationHttpServerInterceptor, который отображает ViolationException в ответ HTTP 400 |
HTTP-сервисы, которые должны автоматически возвращать 400 клиентам |
ValidationModule расширяет ValidatorModule, поэтому подключение ValidationModule даёт вам всё, что предоставляет базовый модуль.
Показанная выше зависимость (validation-module) — правильный выбор для HTTP-сервиса; библиотека, которой нужно только генерировать валидаторы, может зависеть от validation-common и подключать вместо этого ValidatorModule.
Аннотации валидации¶
Аннотации валидации указывают Kora, что нужно проверить в поле, аргументе метода или результате метода.
Их можно применять напрямую, либо вложенная валидация может запускаться через @Valid, когда у типа есть сгенерированный или предоставленный вручную Validator.
Валидация Kora — это не Jakarta Bean Validation
Валидация Kora — это не Jakarta Bean Validation (JSR-380).
Все аннотации ограничений Kora находятся в пакете ru.tinkoff.kora.validation.common.annotation и обрабатываются во время компиляции.
В частности, Kora не поставляет аннотацию ограничения @NotNull: значение по умолчанию является обязательным, а чтобы сделать его необязательным, вы помечаете его любой аннотацией @Nullable (см. Необязательные поля).
Kora действительно распознаёт стандартный маркер @Nonnull / @NotNull (из javax.annotation, jakarta.annotation и подобных пакетов) как явное требование не-null, что важно главным образом для полей JsonNullable.
Структурные аннотации, управляющие валидацией:
@Valid— на классе илиrecordгенерируетValidator<T>для этого типа; на поле, аргументе или результате метода запускает вложенную валидацию черезValidatorсоответствующего типа. Применима к типам, полям, параметрам и методам.@Validate— помечает метод, аргументы и/или результат которого должны быть провалидированы аспектом; параметрfailFastуправляет остановкой на первой ошибке (по умолчанию:false). Применима только к методам.@ValidatedBy— связывает пользовательскую аннотацию ограничения сValidatorFactory, которая строит еёValidator(см. Пользовательские аннотации валидации). Применима только к типам аннотаций.
Встроенные аннотации ограничений и их параметры:
| Аннотация | Поддерживаемые типы | Параметры (значения по умолчанию) | Описание |
|---|---|---|---|
@NotBlank |
String, CharSequence |
— | Значение не null и содержит хотя бы один непробельный символ. |
@NotEmpty |
String, CharSequence, Iterable, Collection, List, Set, Map |
— | Значение не null и не пустое. |
@Pattern |
String, CharSequence |
value (обязательный, без значения по умолчанию), flags (по умолчанию: 0) |
Значение соответствует регулярному выражению value; flags отображается на флаги java.util.regex.Pattern. |
@Range |
Short, Integer, Long, Float, Double, BigInteger, BigDecimal |
from (обязательный, без значения по умолчанию), to (обязательный, без значения по умолчанию), boundary (по умолчанию: INCLUSIVE_INCLUSIVE) |
Число лежит в пределах [from, to]; boundary управляет тем, включаются ли границы. |
@Size |
String, CharSequence, Collection, List, Set, Map |
min (по умолчанию: 0), max (обязательный, без значения по умолчанию) |
Размер (длина) значения находится в пределах min и max. |
Note
Обращайте внимание на обязательные параметры: у @Size.max нет значения по умолчанию, поэтому его пропуск является ошибкой компиляции; @Range.from и @Range.to оба обязательны и объявлены как double.
Значение @Range.boundary — это перечисление Range.Boundary с вариантами EXCLUSIVE_EXCLUSIVE, INCLUSIVE_EXCLUSIVE, EXCLUSIVE_INCLUSIVE и INCLUSIVE_INCLUSIVE.
Валидация класса¶
Аннотация @Valid на классе или record указывает Kora создать Validator<T> для этого типа.
Сгенерированный валидатор становится обычным компонентом графа зависимостей и может быть внедрён по сигнатуре Validator<Type>.
После этого валидатор для данного класса будет доступен в контейнере зависимостей:
Сгенерированные валидаторы можно внедрять как зависимости в любой компонент.
В примере выше валидатор для User внедряется по сигнатуре Validator<User> и может использоваться вручную.
Метод validate(...) возвращает список Violation.
Вы можете обработать этот список самостоятельно или вызвать validateAndThrow(...), который выбрасывает ViolationException, если есть нарушения.
Полный императивный API смотрите в разделе Ручная валидация.
Валидация поля¶
Валидация поля использует набор аннотаций, предоставляемых модулем.
Объект, помеченный для валидации, выглядит так:
Для record доступ к полям осуществляется через методы самого record.
Для Foo и поля number сгенерированный Validator будет использовать метод number().
Для обычного класса используется синтаксис JavaBeans: например, для поля id будет использоваться метод getId().
Этот метод должен иметь как минимум видимость package-private.
Обязательные поля¶
Все поля по умолчанию считаются обязательными, поэтому для них создаются проверки на null.
Необязательные поля¶
Чтобы пометить поле как необязательное, аннотируйте его любой аннотацией @Nullable.
Для такого поля проверка на null не будет создана:
- Подойдёт любая аннотация
@Nullable, напримерjavax.annotation.Nullable,jakarta.annotation.Nullableилиorg.jetbrains.annotations.Nullable.
Чтобы пометить поле как необязательное, используйте синтаксис Kotlin Nullability и добавьте ? к типу поля.
Для такого поля проверка на null не будет создана:
Вложенные поля¶
Используйте @Valid для валидации вложенных объектов, у которых есть сгенерированные или предоставленные вручную валидаторы.
В примере выше для Bar будет создан Validator<Bar>, а для Foo будет создан Validator<Foo>.
При вызове Validator<Foo> он внутри себя вызовет Validator<Bar>.
Валидация коллекции¶
@Valid на поле List, Set или Collection валидирует каждый элемент через Validator элемента.
ValidatorModule предоставляет эти валидаторы элементов из коробки (Validator<List<T>>, Validator<Set<T>>, Validator<Collection<T>>), поэтому дополнительной настройки не требуется.
Каждый Bar в списке валидируется, а путь нарушения индексируется по позиции элемента, например bars[0].number.
Ограничения, такие как @Size, можно комбинировать с @Valid на одной и той же коллекции, чтобы проверить и размер коллекции, и каждый элемент.
Иерархии Sealed¶
Kora может создать Validator для sealed-иерархий.
Если @Valid помещена на sealed-тип, сгенерированный валидатор определяет фактический подтип и вызывает валидатор для соответствующей финальной реализации.
JsonNullable¶
Для JsonNullable<T> Kora валидирует значение T внутри контейнера.
Если JsonNullable находится в состоянии undefined, обычные проверки значения не выполняются.
Используйте @NotNull или @Nonnull, чтобы запретить undefined или null.
Параметры валидации¶
Существует два режима валидации, выбираемых через ValidationContext, передаваемый в validate(...):
Full— проверяются все помеченные поля, собираются все возможные ошибки валидации, и только затем возвращается список нарушений или выбрасывается исключение. Это поведение по умолчанию.FailFast— валидация останавливается на первой найденной ошибке.
ValidationContext можно построить несколькими эквивалентными способами:
ValidationContext.builder().build()— контекстFullпо умолчанию (то же, что и вызовvalidate(value)без контекста).ValidationContext.full()— явный контекстFull.ValidationContext.failFast()— контекстFailFast.ValidationContext.builder().failFast(true).build()— формаFailFastчерез строитель.
Пример валидации FailFast:
Ручная валидация¶
Сгенерированный Validator<T> — это обычный компонент, поэтому его можно внедрить и вызвать напрямую — например, в сервисе, который не является HTTP-контроллером, или когда вы хотите изучить нарушения вместо выбрасывания исключения.
@Component
public final class UserService {
private final Validator<User> validator;
public UserService(Validator<User> validator) {
this.validator = validator;
}
public void process(User user) {
List<Violation> violations = validator.validate(user); //(1)!
if (!violations.isEmpty()) {
Violation first = violations.get(0);
throw new IllegalStateException(first.path().full() + ": " + first.message()); //(2)!
}
}
}
validate(value)собирает все нарушения; используйтеvalidate(value, context), чтобы передать параметры валидации.- Каждый
Violationпредоставляетpath()иmessage().
@Component
class UserService(private val validator: Validator<User>) {
fun process(user: User) {
val violations = validator.validate(user) //(1)!
if (violations.isNotEmpty()) {
val first = violations.first()
throw IllegalStateException("${first.path().full()}: ${first.message()}") //(2)!
}
}
}
validate(value)собирает все нарушения; используйтеvalidate(value, context), чтобы передать параметры валидации.- Каждый
Violationпредоставляетpath()иmessage().
Контракт Validator<T> предлагает следующие методы:
validate(value)/validate(value, context)— возвращаютList<Violation>, который пуст, когда значение валидно (значениеnullзавершается нарушением).validateAndThrow(value)/validateAndThrow(value, context)— выбрасываютViolationExceptionпри возникновении любого нарушения и не делают ничего в противном случае.
Когда ViolationException перехвачено, getViolations() возвращает агрегированный List<Violation>, а getMessage() возвращает предварительно отформатированную многострочную сводку по каждому пути и сообщению нарушения.
Валидация метода¶
Валидация аргументов и результата метода использует аспект @Validate и набор аннотаций, предоставляемых модулем.
Kora генерирует код аспекта во время компиляции, поэтому класс с такими методами должен поддерживать применение аспектов.
Валидация аргумента¶
Чтобы провалидировать аргументы метода, используйте аннотацию @Validate на методе и аннотируйте аргументы нужными ограничениями.
Аргументы можно валидировать аннотациями ограничений напрямую или через @Valid, когда у типа аргумента есть собственный Validator:
@Component
public class ArgumentValidator {
@Valid
public record User(@NotBlank String id,
@Size(min = 3, max = 6) String name,
@Nullable String status) { }
@Validate
public int calculate(@Valid User user, //(1)!
@Range(from = 1, to = 900) int weight, //(2)!
@Pattern("ME\\d+") String code) { //(3)!
return Integer.parseInt(code.substring(2));
}
}
- Вложенная валидация через
Validator<User>. - Ограничение числового диапазона, применённое напрямую к аргументу.
- Ограничение регулярного выражения, применённое напрямую к аргументу.
@Component
open class ArgumentValidator {
@Valid
data class User(@field:NotBlank val id: String,
@field:Size(min = 3, max = 6) val name: String,
val status: String?)
@Validate
fun calculate(@Valid user: User, //(1)!
@Range(from = 1.0, to = 900.0) weight: Int, //(2)!
@Pattern("ME\\d+") code: String): Int { //(3)!
return code.substring(2).toInt()
}
}
- Вложенная валидация через
Validator<User>. - Ограничение числового диапазона, применённое напрямую к аргументу.
- Ограничение регулярного выражения, применённое напрямую к аргументу.
Если какой-либо аргумент не проходит валидацию, аспект выбрасывает ViolationException до выполнения тела метода.
Обязательные аргументы¶
Все аргументы по умолчанию считаются обязательными, поэтому для них создаются проверки на null.
Необязательные аргументы¶
Чтобы пометить аргумент как необязательный, аннотируйте его любой аннотацией @Nullable.
Для такого аргумента проверка на null не будет создана:
@Component
public class SomeService {
@Validate
public int validate(@Nullable String argument) { //(1)!
return 1;
}
}
- Подойдёт любая аннотация
@Nullable, напримерjavax.annotation.Nullable,jakarta.annotation.Nullableилиorg.jetbrains.annotations.Nullable.
Чтобы пометить аргумент как необязательный, используйте синтаксис Kotlin Nullability и добавьте ? к типу аргумента.
Для такого аргумента проверка на null не будет создана:
Вложенные аргументы¶
Используйте @Valid для валидации вложенных аргументов, у которых есть сгенерированные или предоставленные вручную валидаторы.
В примере выше для Foo будет создан Validator<Foo>.
При вызове метода аспект @Validate вызовет этот валидатор для аргумента argument.
Валидация результата¶
Чтобы провалидировать результат метода, используйте аннотацию @Validate на методе и аннотируйте результат соответствующими аннотациями.
Поместите @Valid на метод, чтобы запустить вложенную валидацию через Validator возвращаемого типа.
Чтобы потребовать, чтобы результат был не null, используйте любую аннотацию @Nonnull или @NotNull.
@Component
public class ResultValidator {
@Valid
public record User(@NotBlank String id,
@Size(min = 3, max = 6) String name,
@Nullable @Size(min = 1, max = 10) String status) { } //(1)!
@Valid //(3)!
@Validate //(2)!
public User create(String name, String status) {
return new User(UUID.randomUUID().toString(), name, status);
}
}
- Ограничения можно накладывать друг на друга:
statusнеобязателен (@Nullable), но если он присутствует, его длина должна укладываться в@Size. - Указывает, что метод требует валидации.
- Указывает, что результат должен быть провалидирован через
Validatorвозвращаемого типа.
@Component
open class ResultValidator {
@Valid
data class User(@field:NotBlank val id: String,
@field:Size(min = 3, max = 6) val name: String,
@field:Size(min = 1, max = 10) val status: String?) //(1)!
@Valid //(3)!
@Validate //(2)!
fun create(name: String, status: String): User {
return User(UUID.randomUUID().toString(), name, status)
}
}
- Ограничения можно накладывать друг на друга:
statusнеобязателен (nullable), но если он присутствует, его длина должна укладываться в@Size. - Указывает, что метод требует валидации.
- Указывает, что результат должен быть провалидирован через
Validatorвозвращаемого типа.
Валидация результата выполняется после тела метода, над его возвращаемым значением; если она не проходит, аспект выбрасывает ViolationException вместо возврата значения.
Ограничения также можно применять к самому контейнеру результата. Например, у результата-коллекции можно одновременно проверить размер и провалидировать её элементы:
@Valid
public record Foo(@Valid Bar bar) { }
@Component
public class SomeService {
@Size(min = 1, max = 3) //(3)!
@Valid //(2)!
@Validate //(1)!
public List<Foo> validate() {
// do something
}
}
- Указывает, что метод требует валидации.
- Указывает, что результат должен быть провалидирован через
Validatorвозвращаемого типа. - Стандартная аннотация валидации.
@Component
open class SomeService {
@Size(min = 1, max = 3) //(3)!
@Valid //(2)!
@Validate //(1)!
fun validate(): List<Foo> {
// do something
}
}
- Указывает, что метод требует валидации.
- Указывает, что результат должен быть провалидирован через
Validatorвозвращаемого типа. - Стандартная аннотация валидации.
Параметры валидации¶
Существует два режима валидации:
Full— проверяются все помеченные аргументы и результат, собираются все возможные ошибки валидации, и только затем выбрасывается исключение. Это поведение по умолчанию.FailFast— исключение выбрасывается на первой найденной ошибке.
Пример валидации FailFast:
HTTP обработки ошибок¶
Когда HTTP-сервис Kora использует ValidationModule (из артефакта validation-module), неудачная валидация может быть автоматически превращена в ответ HTTP 400 вместо неперехваченной ошибки.
Это обрабатывается ValidationHttpServerInterceptor — перехватчиком HTTP-сервера, который перехватывает ViolationException, выброшенное аспектом @Validate (включая исключение, обёрнутое в CompletionException для асинхронных сигнатур), и формирует ответ.
По умолчанию он возвращает статус 400 с сообщением ViolationException в качестве тела в формате обычного текста; пользовательский маппер ответа может заменить это.
Глобальные перехватчики собираются по тегу @Tag(HttpServerModule.class) (см. Перехватчики), поэтому перехватчик должен быть предоставлен с этим тегом, чтобы применяться к каждому маршруту:
@KoraApp
public interface Application extends
ValidationModule, //(1)!
UndertowHttpServerModule,
JsonModule {
@Tag(HttpServerModule.class) //(2)!
default ValidationHttpServerInterceptor validationHttpServerInterceptor(@Nullable ViolationExceptionHttpServerResponseMapper mapper) {
return new ValidationHttpServerInterceptor(mapper); //(3)!
}
}
ValidationModuleрасширяетValidatorModuleи предоставляет связываниеValidationHttpServerInterceptorиViolationExceptionHttpServerResponseMapper.- Регистрирует перехватчик как глобальный перехватчик HTTP-сервера.
- Передача
nullв качестве маппера сохраняет ответ400по умолчанию в формате обычного текста.
@KoraApp
interface Application : ValidationModule, //(1)!
UndertowHttpServerModule,
JsonModule {
@Tag(HttpServerModule::class) //(2)!
fun validationInterceptor(mapper: ViolationExceptionHttpServerResponseMapper?): ValidationHttpServerInterceptor {
return ValidationHttpServerInterceptor(mapper) //(3)!
}
}
ValidationModuleрасширяетValidatorModuleи предоставляет связываниеValidationHttpServerInterceptorиViolationExceptionHttpServerResponseMapper.- Регистрирует перехватчик как глобальный перехватчик HTTP-сервера.
- Передача
nullв качестве маппера сохраняет ответ400по умолчанию в формате обычного текста.
Метод контроллера, аннотированный @Validate, затем формирует 400 для клиента всякий раз, когда его аргументы или результат не проходят валидацию, без настройки для каждого контроллера:
@Json
public record UserRequest(@NotBlank @Size(min = 2, max = 100) String name,
@NotBlank @Pattern("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$") String email) { }
@Component
@HttpController
public final class UserController {
@HttpRoute(method = HttpMethod.POST, path = "/users")
@Validate //(1)!
@Json
public UserResponse createUser(@Valid @Json UserRequest request) { //(2)!
// request is already validated here
}
}
- Включает валидацию аргументов (и результата) для этого маршрута.
- Вложенная валидация тела запроса; нарушение приводит к
HTTP400до выполнения тела.
@Json
data class UserRequest(@field:NotBlank @field:Size(min = 2, max = 100) val name: String,
@field:NotBlank @field:Pattern("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$") val email: String)
@Component
@HttpController
class UserController {
@HttpRoute(method = HttpMethod.POST, path = "/users")
@Validate //(1)!
@Json
fun createUser(@Valid @Json request: UserRequest): UserResponse {
// request is already validated here
}
}
- Включает валидацию аргументов (и результата) для этого маршрута.
- Вложенная валидация тела запроса; нарушение приводит к
HTTP400до выполнения тела.
Пользовательский ответ¶
Чтобы управлять статусом, заголовками или телом ответа — например, чтобы вернуть структурированную ошибку JSON вместо обычного текста по умолчанию — предоставьте компонент ViolationExceptionHttpServerResponseMapper.
Его метод apply(request, exception) возвращает HttpServerResponse для отправки; возврат null откатывается к ответу 400 по умолчанию в формате обычного текста.
@Json //(1)!
public record ValidationErrorResponse(String code, String message, List<ValidationErrorDetails> errors) { }
@Json
public record ValidationErrorDetails(String field, String message) { }
@KoraApp
public interface Application extends
ValidationModule,
UndertowHttpServerModule,
JsonModule {
default ViolationExceptionHttpServerResponseMapper violationExceptionMapper(JsonWriter<ValidationErrorResponse> writer) {
return (request, exception) -> {
var errors = exception.getViolations().stream() //(2)!
.map(v -> new ValidationErrorDetails(v.path().full(), v.message()))
.toList();
var body = new ValidationErrorResponse("VALIDATION_ERROR", "Validation failed", errors);
return HttpServerResponse.of(400, HttpBody.json(writer.toByteArrayUnchecked(body))); //(3)!
};
}
@Tag(HttpServerModule.class)
default ValidationHttpServerInterceptor validationHttpServerInterceptor(ViolationExceptionHttpServerResponseMapper mapper) {
return new ValidationHttpServerInterceptor(mapper);
}
}
- Сериализуется с помощью модуля JSON.
ViolationException.getViolations()возвращает каждыйViolation;path().full()— это путь через точку (например,customer.address.city).- Может быть возвращён любой
HttpServerResponse; возвратnullоткатился бы к400по умолчанию.
@Json //(1)!
data class ValidationErrorResponse(val code: String, val message: String, val errors: List<ValidationErrorDetails>)
@Json
data class ValidationErrorDetails(val field: String, val message: String)
@KoraApp
interface Application : ValidationModule,
UndertowHttpServerModule,
JsonModule {
fun violationExceptionMapper(writer: JsonWriter<ValidationErrorResponse>): ViolationExceptionHttpServerResponseMapper {
return ViolationExceptionHttpServerResponseMapper { request, exception ->
val errors = exception.violations.map { //(2)!
ValidationErrorDetails(it.path().full(), it.message())
}
val body = ValidationErrorResponse("VALIDATION_ERROR", "Validation failed", errors)
HttpServerResponse.of(400, HttpBody.json(writer.toByteArrayUnchecked(body))) //(3)!
}
}
@Tag(HttpServerModule::class)
fun validationInterceptor(mapper: ViolationExceptionHttpServerResponseMapper): ValidationHttpServerInterceptor {
return ValidationHttpServerInterceptor(mapper)
}
}
- Сериализуется с помощью модуля JSON.
ViolationException.getViolations()возвращает каждыйViolation;path().full()— это путь через точку (например,customer.address.city).- Может быть возвращён любой
HttpServerResponse; возвратnullоткатился бы к400по умолчанию.
Пользовательские аннотации валидации¶
Пользовательская аннотация валидации нужна, когда стандартных проверок недостаточно.
Она связывает аннотацию с ValidatorFactory, и фабрика создаёт Validator для конкретного типа значения.
Чтобы создать пользовательскую аннотацию:
- Создайте реализацию
Validator:
final class MyValidStringValidator implements Validator<String> {
@Nonnull
@Override
public List<Violation> validate(String value, @Nonnull ValidationContext context) {
if (value == null) {
return List.of(context.violates("Should be not empty, but was null"));
} else if (value.isEmpty()) {
return List.of(context.violates("Should be not empty, but was empty"));
}
return Collections.emptyList();
}
}
class MyValidStringValidator : Validator<String?> {
override fun validate(value: String?, context: ValidationContext): List<Violation> {
if (value == null) {
return listOf(context.violates("Should be not empty, but was null"))
} else if (value.isEmpty()) {
return listOf(context.violates("Should be not empty, but was empty"))
}
return listOf()
}
}
- Создайте подтип
ValidatorFactory:
- Зарегистрируйте
ValidatorFactoryкак компонент:
- Создайте аннотацию валидации и пометьте её
@ValidatedBy, используя ранее созданный подтипValidatorFactory:
- Пометьте поле, аргумент или результат новой аннотацией:
Параметризованные ограничения¶
Пользовательская аннотация ограничения может объявлять параметры.
Когда это так, её подтип ValidatorFactory должен объявить метод create(...), список параметров которого совпадает с атрибутами аннотации (то же количество параметров, в порядке объявления).
Kora читает значения аннотации (с применёнными значениями по умолчанию) во время компиляции и передаёт их в этот метод create(...); если подходящей перегрузки create(...) не существует, сборка завершается ошибкой.
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@ValidatedBy(PrefixedValidatorFactory.class)
public @interface Prefixed {
String value(); //(1)!
}
public interface PrefixedValidatorFactory extends ValidatorFactory<String> {
@Override
default Validator<String> create() { //(2)!
throw new UnsupportedOperationException("Prefix is required");
}
Validator<String> create(String prefix); //(3)!
}
- Единственный атрибут аннотации.
- Унаследованный фабричный метод без аргументов непригоден для этого ограничения.
- Соответствующий
create(...)с одним параметром; Kora передаётvalue()вprefix.
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
@ValidatedBy(PrefixedValidatorFactory::class)
annotation class Prefixed(val value: String) //(1)!
interface PrefixedValidatorFactory : ValidatorFactory<String> {
override fun create(): Validator<String> = //(2)!
throw UnsupportedOperationException("Prefix is required")
fun create(prefix: String): Validator<String> //(3)!
}
- Единственный атрибут аннотации.
- Унаследованный фабричный метод без аргументов непригоден для этого ограничения.
- Соответствующий
create(...)с одним параметром; Kora передаётvalueвprefix.
Фабрика регистрируется как компонент точно так же, как и в случае без параметров (шаг 3 выше). Это тот же механизм, который используют встроенные ограничения, и их публичные интерфейсы фабрик предоставляют переиспользуемые перегрузки, которым может делегировать пользовательская фабрика:
RangeValidatorFactory—create(double from, double to)иcreate(double from, double to, Range.Boundary boundary).SizeValidatorFactory—create(int to)иcreate(int from, int to).PatternValidatorFactory—create(String pattern)иcreate(String pattern, int flags).NotEmptyValidatorFactoryиNotBlankValidatorFactory—create()без параметров.
Сигнатуры¶
Сигнатуры методов, поддерживаемые аспектом @Validate из коробки:
Класс не должен быть final, чтобы аспекты работали.
T означает тип возвращаемого значения.
T myMethod()Optional<T> myMethod()CompletionStage<T> myMethod()CompletionStageMono<T> myMethod()Project Reactor (требует зависимость)Flux<T> myMethod()Project Reactor (требует зависимость)
Класс должен быть open, чтобы аспекты работали.
T означает тип возвращаемого значения, T? или Unit.
myMethod(): Tsuspend myMethod(): TKotlin Coroutine (требует зависимость какimplementation)myMethod(): Flow<T>Kotlin Coroutine (требует зависимость какimplementation)