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));
});

Рекомендуемые программы

Завершено

0 / 23