Агрегация ошибок валидации в Validator.js представляет собой процесс сбора, структурирования и унификации сообщений об ошибках, возникающих при проверке входных данных. В контексте веб-приложений это критически важный механизм, поскольку позволяет формировать согласованный ответ для клиента при одновременном нарушении нескольких правил валидации.
В стандартной модели Validator.js каждая функция проверки возвращает
булево значение: true или false. При этом
библиотека не навязывает структуру хранения ошибок, оставляя агрегацию
на стороне разработчика.
Пример базового подхода:
import validator from 'validator';
const errors = [];
if (!validator.isEmail(email)) {
errors.push('Некорректный email');
}
if (!validator.isLength(password, { min: 8 })) {
errors.push('Пароль слишком короткий');
}
Такой подход формирует линейный список ошибок, однако не учитывает структуру данных и не предоставляет механизмов группировки.
Агрегация ошибок — это процесс преобразования разрозненных результатов валидации в единый структурированный объект. Чаще всего используется один из следующих форматов:
Ключевая цель — обеспечить предсказуемый формат ответа независимо от количества нарушенных правил.
Наиболее распространённый подход — объект, где ключ соответствует полю данных:
const errors = {};
if (!validator.isEmail(form.email)) {
errors.email = errors.email || [];
errors.email.push('Некорректный email');
}
if (!validator.isLength(form.password, { min: 8 })) {
errors.password = errors.password || [];
errors.password.push('Минимальная длина 8 символов');
}
В результате формируется структура:
{
email: ['Некорректный email'],
password: ['Минимальная длина 8 символов']
}
Такой формат удобен для фронтенда, поскольку позволяет напрямую привязывать ошибки к полям формы.
При увеличении количества проверок ручное добавление ошибок становится неэффективным. Вводится вспомогательный слой:
function addError(errors, field, message) {
if (!errors[field]) {
errors[field] = [];
}
errors[field].push(message);
}
Использование:
const errors = {};
if (!validator.isEmail(form.email)) {
addError(errors, 'email', 'Некорректный email');
}
if (!validator.isInt(form.age, { min: 18 })) {
addError(errors, 'age', 'Возраст должен быть не меньше 18');
}
Это упрощает расширение правил и снижает дублирование кода.
При обработке сложных форм часто применяется декларативный подход:
const rules = {
email: (value) => validator.isEmail(value) || 'Некорректный email',
password: (value) =>
validator.isLength(value, { min: 8 }) || 'Слишком короткий пароль',
};
Функция агрегации:
function validate(data, rules) {
const errors = {};
Object.keys(rules).forEach((field) => {
const result = rules[field](data[field]);
if (result !== true) {
addError(errors, field, result);
}
});
return errors;
}
Такой подход делает валидацию масштабируемой и удобной для переиспользования.
В реальных системах данные часто имеют вложенную структуру:
const form = {
user: {
email: '',
profile: {
age: 16,
},
},
};
Для таких случаев применяется путь в виде строки:
const errors = {};
addError(errors, 'user.email', 'Некорректный email');
addError(errors, 'user.profile.age', 'Недопустимый возраст');
Результат:
{
"user.email": ["Некорректный email"],
"user.profile.age": ["Недопустимый возраст"]
}
Альтернативный вариант — вложенные объекты:
{
user: {
email: ['Некорректный email'],
profile: {
age: ['Недопустимый возраст']
}
}
}
Выбор структуры зависит от требований клиентской части и формата API.
При комбинировании нескольких источников валидации (например, клиентской и серверной) требуется нормализация.
Пример объединения:
function mergeErrors(target, source) {
Object.keys(source).forEach((key) => {
if (!target[key]) {
target[key] = [];
}
target[key].push(...source[key]);
});
return target;
}
Это позволяет объединять результаты разных валидаторов без потери данных.
При последовательной проверке важно предотвращать избыточные вычисления:
const errors = {};
if (validator.isEmpty(form.email)) {
addError(errors, 'email', 'Email обязателен');
} else if (!validator.isEmail(form.email)) {
addError(errors, 'email', 'Некорректный email');
}
Использование else if снижает количество ошибок в одном
поле, формируя более строгую приоритетность.
В некоторых сценариях требуется сохранять все возможные нарушения:
if (!validator.isLength(password, { min: 8 })) {
addError(errors, 'password', 'Минимум 8 символов');
}
if (!validator.matches(password, /[A-Z]/)) {
addError(errors, 'password', 'Требуется заглавная буква');
}
if (!validator.matches(password, /[0-9]/)) {
addError(errors, 'password', 'Требуется цифра');
}
Результат:
{
password: [
'Минимум 8 символов',
'Требуется заглавная буква',
'Требуется цифра'
]
}
Такая модель используется в системах с повышенными требованиями к качеству ввода.
В серверных приложениях агрегация ошибок часто становится частью middleware:
function validationMiddleware(req, res, next) {
const errors = validate(req.body, rules);
if (Object.keys(errors).length > 0) {
return res.status(400).json({ errors });
}
next();
}
Это обеспечивает единообразный формат ошибок на уровне API.
При больших формах важно минимизировать размер ответа:
Пример:
{
email: ['ERR_INVALID_EMAIL'],
password: ['ERR_PASSWORD_TOO_SHORT']
}
Такая модель снижает нагрузку на сеть и упрощает интернационализацию.
Существует несколько стратегий:
1. Стоп-режим (fail-fast) Остановка после первой ошибки.
2. Полная агрегация Сбор всех возможных ошибок.
3. Приоритетная агрегация Фиксация только критических нарушений.
Выбор модели влияет на пользовательский опыт и нагрузку на систему валидации.
При масштабировании системы ключевым становится единый контракт ошибок. Независимо от количества полей и сложности правил, структура должна оставаться предсказуемой: