В коде ниже данные от пользователей принимаются и сохраняются как есть. То есть мы полагаем, что пользователи вводят данные корректно:
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));
});
Самостоятельная работа
- Выполните на своем компьютере все шаги из урока
- Модифицируйте форму для добавления нового пользователя и ее обработчик так, чтобы программа валидировала данные от пользователя
- Сделайте то же самое для курсов. При добавлении нового курса программа должна проверять, чтобы название курса было длиннее 2 символов, а описания курса — длиннее 10 символов
- Запустите приложение и попробуйте добавлять новые сущности
- Проверьте, что при вводе некорректных данных форма остается заполненной и отображает сообщения об ошибках
- Залейте изменения на GitHub
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.