Как провалидировать одно поле в зависимости от другого в Spring, например дата от раньше даты до
Что проверяют
Знаете ли вы, что аннотации на полях проверяют каждое поле отдельно, а связь между полями требует проверки на уровне класса. Следом обычно спрашивают, как привязать ошибку к конкретному полю, чтобы фронтенд подсветил именно «дату до», и как это тестировать.
Как отвечать
Основной способ: своя аннотация-ограничение на уровне класса.
- аннотация:
@Constraint(validatedBy = DateRangeValidator.class),@Target(ElementType.TYPE),@Retention(RUNTIME)и обязательныеmessage,groups,payload. В Spring Boot 3 это пакетjakarta.validation, а неjavax.validation; - валидатор:
ConstraintValidator<ValidDateRange, DateRange>, вisValidсравнение!from.isAfter(to)илиfrom.isBefore(to), смотря разрешён ли один и тот же день, это стоит уточнить; - null: если одно из полей пустое, возвращайте
true, обязательность оставьте@NotNullна полях. Иначе на одну проблему придут две ошибки; - ошибка на поле: по умолчанию нарушение привязано к объекту целиком. Чтобы подсветить поле, вызовите
context.disableDefaultConstraintViolation()и соберите нарушение черезbuildConstraintViolationWithTemplate(...).addPropertyNode("to").addConstraintViolation(); - подключение:
@Valid @RequestBodyв контроллере. При ошибке Spring бросаетMethodArgumentNotValidException, его удобно превращать в ответ 400 сProblemDetailв@RestControllerAdvice.
Быстрая альтернатива для одного DTO: метод @AssertTrue boolean isPeriodValid() прямо в классе. Для переиспользования делают аннотацию с именами полей: @DateRange(start = "from", end = "to").
Тестируют через настоящий Validator, а не вызовом isValid(dto, null): так проверяется и аннотация, и путь ошибки.
Где спотыкаются
Ставят аннотацию на поле и пытаются из валидатора добраться до соседнего. Валидатор поля видит только своё значение, ему нужен весь объект.
Забывают @Valid на параметре контроллера. Аннотации на DTO есть, но проверка не запускается, и кажется, что валидация сломана.
Импортируют javax.validation в проекте на Spring Boot 3. Если старая зависимость осталась в classpath, код скомпилируется, но Hibernate Validator такие аннотации проигнорирует.