Зарегистрируйтесь, чтобы продолжить обучение

Валидация Java: Веб-технологии

В коде ниже данные от пользователей принимаются и сохраняются как есть. То есть мы полагаем, что пользователи вводят данные корректно:

app.post("/users", ctx -> {
    var name = ctx.formParam("name");
    var email = ctx.formParam("email");
    var password = ctx.formParam("password");
    var passwordConfirmation = ctx.formParam("passwordConfirmation");

    var user = new User(name, email, password);
    UserRepository.save(user);
    ctx.redirect("/users");
});

На практике такого не бывает. Данные могут быть неполными, ошибочными или неподходящими для нашей системы. В этом уроке мы научимся работать с такими данными.

Что такое валидация

Получив данные от пользователя, мы должны их проверить. Такая проверка называется валидацией. Валидация включает два основных элемента:

  • Проверка входных данных на корректность — например, заполнены ли обязательные поля или совпадает ли пароль и его подтверждения
  • Проверка возможности выполнить операцию — например, мы не сможем зарегистрировать нового пользователя, если он пытается ввести почту, связанную с уже существующим аккаунтом

Как проверить корректность данных

Проверить корректность введенного пароля можно простым сравнением:

var password = ctx.formParam("password");
var passwordConfirmation = ctx.formParam("passwordConfirmation");

if (!password.equals(passwordConfirmation)) {
    // Что-то делаем, если пароли не совпали
}

Что делать дальше? Будет логично, если мы отобразим ту же форму с двумя важными дополнениями:

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

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

Шаг 1. Создаем дата-класс, который передает в шаблон данные формы и ошибки. Ошибки передаются в виде объекта, который формирует Javalin в случае ошибки валидации:

   package org.example.hexlet.dto.users;

   import java.util.List;
   import java.util.Map;

   import io.javalin.validation.ValidationError;
   import lombok.AllArgsConstructor;
   import lombok.Getter;
   import lombok.NoArgsConstructor;

   @AllArgsConstructor
   @NoArgsConstructor
   @Getter
   public class BuildUserPage {
       private String name;
       private String email;
       private Map<String, List<ValidationError<Object>>> errors;
   }

Шаг 2. Расширяем обработчик. Добавляем встроенный механизм валидации Javalin и обработку его исключений:

  app.post("/users", ctx -> {
      var name = ctx.formParam("name");
      var email = ctx.formParam("email");

      try {
          var passwordConfirmation = ctx.formParam("passwordConfirmation");
          var password = ctx.formParamAsClass("password", String.class)
                  .check(value -> value.equals(passwordConfirmation), "Пароли не совпадают")
                  .get();
          var user = new User(name, email, password);
          UserRepository.save(user);
          ctx.redirect("/users");
      } catch (ValidationException e) {
          var page = new BuildUserPage(name, email, e.getErrors());
          ctx.render("users/build.jte", model("page", page));
      }
  });

Валидация в Javalin работает через методы *AsClass. Они возвращают валидатор, который содержит набор методов для дополнительных проверок. Здесь мы использовали метод check(), который принимает на вход два параметра:

  • Функцию-предикат, в которой выполняется проверка
  • Строку, которую мы выведем при проваленной проверке

Если валидация провалена, Javalin выкидывает исключение ValidationException. Далее мы перехватываем его, чтобы отрисовать форму. Затем мы идем в блок catch, берем данные формы и это исключение, после чего передаем все в шаблон users/build.jte на отрисовку.

Шаг 3. Заполняем шаблон данными и выводим ошибки:

  @import org.example.hexlet.dto.users.BuildUserPage
  @param BuildUserPage page

  @if(page.getErrors() != null)
      <ul>
          @for(var validator : page.getErrors().values())
              @for(var error : validator)
                  <li>${error.getMessage()}</li>
              @endfor
          @endfor
      </ul>
  @endif

  <form action="/users" method="post">
    <div>
      <label>
        Имя
        <input type="text" name="name" value="${page.getName()}" />
      </label>
    </div>
    <div>
      <label>
        Email
        <input type="email" required name="email" value="${page.getEmail()}" />
      </label>
    </div>
    <div>
      <label>
        Пароль
        <input type="password" required name="password" />
      </label>
    </div>
    <div>
      <label>
        Подтверждение пароля
        <input type="password" required name="passwordConfirmation" />
      </label>
    </div>
    <input type="submit" value="Зарегистрировать" />
  </form>

Чтобы заполнить данные, достаточно применить интерполяцию в нужных элементах формы. А вот вывод ошибок выглядит чуть сложнее. Все дело в структуре объектов для представления ошибок.

Каждая ошибка представляет собой Map, потому что один валидатор может генерировать несколько ошибок. Например, мы можем добавить проверку на длину пароля:

  var password = ctx.formParamAsClass("password", String.class)
          .check(value -> value.equals(passwordConfirmation), "Пароли не совпадают")
          .check(value -> value.length() > 6, "У пароля недостаточная длина")
          .get();

Шаг 4. Дополняем обработчик формы создания пользователя, потому что теперь его шаблон работает с BuildUserPage:

  app.get("/users/build", ctx -> {
      var page = new BuildUserPage();
      ctx.render("users/build.jte", model("page", page));
  });

Самостоятельная работа

  1. Выполните на своем компьютере все шаги из урока
  2. Модифицируйте форму для добавления нового пользователя и ее обработчик так, чтобы программа валидировала данные от пользователя
  3. Сделайте то же самое для курсов. При добавлении нового курса программа должна проверять, чтобы название курса было длиннее 2 символов, а описания курса — длиннее 10 символов
  4. Запустите приложение и попробуйте добавлять новые сущности
  5. Проверьте, что при вводе некорректных данных форма остается заполненной и отображает сообщения об ошибках
  6. Залейте изменения на GitHub

Дополнительные материалы

  1. Официальная документация

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Программирование на Java, Разработка веб-приложений и микросервисов используя Spring Boot, проектирование REST API
10 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»