Вопрос или проблема
У меня есть пользовательский валидатор, и мне нужно включить параметры сообщения, чтобы динамически формировать сообщение об ошибке.
public class DescriptionLengthValidator
implements ConstraintValidator<ValidTypeDescriptionLength, RequestDto> {
@Override
public boolean isValid(RequestDto request, ConstraintValidatorContext context) {
int maxSize = request.getType().equals("Category1") ? 50 : 250;
boolean isValid = request.getDescription().length() <= maxSize;
if (!isValid) {
HibernateConstraintValidatorContext hibernateContext =
context.unwrap(HibernateConstraintValidatorContext.class);
hibernateContext.disableDefaultConstraintViolation();
hibernateContext.addMessageParameter("0", "0").addMessageParameter("1", maxSize)
.buildConstraintViolationWithTemplate("error.Size").addPropertyNode("description")
.addConstraintViolation();
return false;
}
return true;
}
}
Аннотация определена следующим образом.
@Constraint(validatedBy = DescriptionLengthValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidTypeDescriptionLength {
String message() default "error.Size";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Контроллер-совет выглядит следующим образом.
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, String> fieldErrors = ex.getBindingResult().getFieldErrors().stream().collect(
Collectors.toMap(FieldError::getField, fe -> getFieldErrorMessage(fe,
String.format("Недопустимое значение [%s]", fe.getRejectedValue()))));
return handleFieldValidationErrors(ex, status, request, fieldErrors);
}
private String getFieldErrorMessage(FieldError error, String defaultMessage) {
Object[] args = error.getArguments();
if (args != null && args.length > 0 && args[0] instanceof MessageSourceResolvable) {
args = Arrays.copyOfRange(args, 1, args.length);
}
List<String> errorCodes = new ArrayList<>();
if (error.getCodes() != null) {
Arrays.stream(error.getCodes()).map(s -> "error." + s).forEach(errorCodes::add);
}
if (error.getDefaultMessage() != null) {
errorCodes.add(error.getDefaultMessage());
}
return service.getMessage(args, defaultMessage);
}
}
При доступе к аргументам, вызывая error.getArguments(), параметры сообщения недоступны. Как я могу изменить пользовательский валидатор/совет контроллера, чтобы получить доступ к параметрам сообщения?
Ответ или решение
Для того чтобы динамически формировать сообщение об ошибке валидации с помощью параметров сообщения, необходимо правильно передать их из пользовательского валидатора в контроллер. Рассмотрим решение этой задачи, опираясь на представленный вами код.
Актуальная проблема
Ваш текущий код валидатора (DescriptionLengthValidator
) использует метод addMessageParameter
для передачи параметров сообщения. Однако, вы сталкиваетесь с тем, что при обработке в контроллере (в RestResponseEntityExceptionHandler
) аргументы сообщения недоступны при вызове error.getArguments()
.
Оптимальное решение
-
Передача параметров: Убедитесь, что параметры, передаваемые через
addMessageParameter
, корректно указываются и затем получаются в обработчике. Например, в вашем валидаторе можно модифицировать код следующим образом:hibernateContext.addMessageParameter("maxSize", maxSize);
Здесь мы передаем параметр
maxSize
, который может быть использован позже для формирования текстового сообщения. -
Изменение метода для получения ошибок: В методе
getFieldErrorMessage
вRestResponseEntityExceptionHandler
, убедитесь, что вы правильно обрабатываете значения аргументов:private String getFieldErrorMessage(FieldError error, String defaultMessage) { Object[] args = error.getArguments(); // Обработка параметров if (args != null && args.length > 0) { // Например, если аргументы являются массивом, который содержит не только MessageSourceResolvable StringBuilder messageBuilder = new StringBuilder(); for (Object arg : args) { messageBuilder.append(arg.toString()).append(", "); } String messageParams = messageBuilder.toString(); // Объедините параметры, если это необходимо, иначе просто оставьте аргументы. defaultMessage = String.format(defaultMessage, messageParams); } // Логика для обработки сообщения об ошибке List<String> errorCodes = new ArrayList<>(); if (error.getCodes() != null) { Arrays.stream(error.getCodes()).map(s -> "error." + s).forEach(errorCodes::add); } if (error.getDefaultMessage() != null) { errorCodes.add(error.getDefaultMessage()); } return service.getMessage(args, defaultMessage); }
-
Использование форматирования:
Когда вы формируете сообщение, можете воспользоваться форматированием строк. Например:String dynamicMessage = String.format("Длина описания не должна превышать %d символов", (Integer) error.getArguments()[0]);
Заключение
Следуя описанным рекомендациям, вы сможете корректно передавать параметры сообщений от пользовательского валидатора в контроллер и формировать нужные сообщения об ошибках динамически. Это не только улучшит UX, делая сообщения более информативными, но и упростит дальнейшую поддержку кода.
Использование данных подходов позволит вам гибко настраивать валидацию в вашем приложении, увеличивая его надежность и понятность для пользователей.