Получите параметры сообщения в контроллере advice, переданные от пользовательского валидатора

Вопрос или проблема

У меня есть пользовательский валидатор, и мне нужно включить параметры сообщения, чтобы динамически формировать сообщение об ошибке.

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().

Оптимальное решение

  1. Передача параметров: Убедитесь, что параметры, передаваемые через addMessageParameter, корректно указываются и затем получаются в обработчике. Например, в вашем валидаторе можно модифицировать код следующим образом:

    hibernateContext.addMessageParameter("maxSize", maxSize);

    Здесь мы передаем параметр maxSize, который может быть использован позже для формирования текстового сообщения.

  2. Изменение метода для получения ошибок: В методе 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);
    }
  3. Использование форматирования:
    Когда вы формируете сообщение, можете воспользоваться форматированием строк. Например:

    String dynamicMessage = String.format("Длина описания не должна превышать %d символов", (Integer) error.getArguments()[0]);

Заключение

Следуя описанным рекомендациям, вы сможете корректно передавать параметры сообщений от пользовательского валидатора в контроллер и формировать нужные сообщения об ошибках динамически. Это не только улучшит UX, делая сообщения более информативными, но и упростит дальнейшую поддержку кода.

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

Оцените материал
Добавить комментарий

Капча загружается...