Система валидации в библиотеке Vest строится вокруг декларативных
тестов, где ошибки являются результатом выполнения сценария
(suite). В отличие от императивных валидаторов, Vest хранит
состояние проверок, отслеживает проваленные тесты и предоставляет
централизованный API для получения информации об ошибках.
Ошибки в Vest делятся на несколько категорий:
warn);Библиотека не выбрасывает исключения для обычных ошибок валидации.
Вместо этого создаётся объект состояния (suite result),
содержащий сведения о проваленных проверках.
getErrorsimport { create, test, enforce } from 'vest';
const suite = create((data = {}) => {
test('email', 'Некорректный email', () => {
enforce(data.email).matches(/^\S+@\S+\.\S+$/);
});
test('password', 'Пароль слишком короткий', () => {
enforce(data.password).longerThanOrEquals(8);
});
});
const result = suite({
email: 'wrong',
password: '123'
});
console.log(result.getErrors());
Результат:
{
email: ['Некорректный email'],
password: ['Пароль слишком короткий']
}
Метод getErrors() возвращает объект, где:
Даже если ошибка одна, Vest всегда использует массив.
getErrors(fieldName)const emailErrors = result.getErrors('email');
console.log(emailErrors);
Результат:
['Некорректный email']
Это особенно полезно при интеграции с UI-компонентами.
hasErrorsif (result.hasErrors()) {
console.log('Форма содержит ошибки');
}
Проверка конкретного поля:
if (result.hasErrors('email')) {
console.log('Ошибка email');
}
getErrorconst error = result.getError('email');
console.log(error);
Результат:
'Некорректный email'
Используется в интерфейсах, где отображается только одна ошибка.
isValidif (result.isValid()) {
console.log('Форма валидна');
}
Проверка конкретного поля:
result.isValid('password');
Метод является инверсией hasErrors.
test('username', 'Имя пользователя обязательно', () => {
enforce(data.username).isNotBlank();
});
Сообщение может зависеть от данных.
test('age', () => {
const age = data.age;
enforce(age).greaterThan(17);
}, `Возраст ${data.age} недопустим`);
test('password', () => {
if (data.password.length < 8) {
throw new Error('Минимум 8 символов');
}
});
Vest автоматически перехватывает Error.
Если внутри test возникает ошибка:
test('profile', () => {
JSON.parse('{ invalid json }');
});
Vest помечает тест как проваленный.
Ошибки валидации:
enforce(data.age).greaterThan(18);
Системные ошибки:
const x = undefined.name;
Vest умеет обрабатывать оба типа, но системные ошибки желательно логировать отдельно.
test('config', () => {
try {
riskyOperation();
} catch (e) {
throw new Error('Ошибка обработки конфигурации');
}
});
test('email', 'Email уже используется', async () => {
const exists = await api.checkEmail(data.email);
enforce(exists).isFalsy();
});
Vest отслеживает состояние Promise и обновляет результат после завершения.
isPendingif (result.isPending()) {
console.log('Проверка выполняется');
}
Для поля:
result.isPending('email');
test('server', async () => {
await fetchData();
});
Если fetchData() выбросит исключение или вернёт rejected
Promise, тест завершится ошибкой.
test('email', async () => {
try {
await api.validateEmail(data.email);
} catch (e) {
throw new Error('Сервер недоступен');
}
});
warnVest поддерживает предупреждения, не блокирующие валидность формы.
import { create, test, warn } from 'vest';
const suite = create(data => {
test('password', 'Слабый пароль', () => {
warn();
enforce(data.password).longerThanOrEquals(12);
});
});
hasWarningsresult.hasWarnings();
Для поля:
result.hasWarnings('password');
result.getWarnings();
Или:
result.getWarnings('password');
groupimport { group } from 'vest';
group('auth', () => {
test('email', 'Неверный email', () => {
enforce(data.email).matches(/@/);
});
test('password', 'Слишком короткий пароль', () => {
enforce(data.password).longerThan(7);
});
});
result.hasErrorsByGroup('auth');
Получение ошибок:
result.getErrorsByGroup('auth');
onlysuite(data, 'email');
Выполняется только проверка поля email.
suite(data, ['email', 'password']);
Ошибки остальных полей игнорируются.
skipimport { skipWhen } from 'vest';
skipWhen(result.hasErrors('email'), () => {
test('password', 'Пароль обязателен', () => {
enforce(data.password).isNotBlank();
});
});
enforce.extend({
isPhone(value) {
return /^\+\d+$/.test(value);
}
});
Использование:
test('phone', 'Телефон некорректен', () => {
enforce(data.phone).isPhone();
});
const messages = {
required: 'Поле обязательно',
invalidEmail: 'Некорректный email'
};
test('email', messages.invalidEmail, () => {
enforce(data.email).matches(/@/);
});
const i18n = {
ru: {
required: 'Поле обязательно'
},
en: {
required: 'Field is required'
}
};
test('name', i18n[locale].required, () => {
enforce(data.name).isNotBlank();
});
const allErrors = result.getErrors();
const formatted = Object.entries(result.getErrors())
.map(([field, errors]) => ({
field,
errors
}));
{result.hasErrors('email') && (
<span>{result.getError('email')}</span>
)}
<ul>
{result.getErrors('password').map(error => (
<li key={error}>{error}</li>
))}
</ul>
data.users.forEach((user, index) => {
test(`users.${index}.email`, 'Некорректный email', () => {
enforce(user.email).matches(/@/);
});
});
result.getErrors('users.0.email');
result.isPending();
result.tested('email');
Позволяет не показывать ошибки до первого взаимодействия пользователя.
test('password', 'Пароль обязателен', () => {
enforce(data.password).isNotBlank();
});
skipWhen(result.hasErrors('password'), () => {
test('confirmPassword', 'Пароли не совпадают', () => {
enforce(data.confirmPassword).equals(data.password);
});
});
omitWhenimport { omitWhen } from 'vest';
omitWhen(data.isGuest, () => {
test('address', 'Адрес обязателен', () => {
enforce(data.address).isNotBlank();
});
});
Если условие истинно, тесты не попадут в итоговый результат вообще.
test('token', 'Токен недействителен', () => {
enforce(data.token).isNotBlank();
});
После провала критических тестов можно прерывать дальнейшую обработку логикой приложения.
console.log(result);
Объект содержит:
result.tested('email');
Распространённый подход:
const validationResult = suite(data);
const errors = validationResult.getErrors();
return {
valid: validationResult.isValid(),
errors
};
Обычно ошибки делят на:
Vest позволяет хранить их в едином пайплайне валидации.
Плохо:
test('field', 'Ошибка 1', () => {});
test('field', 'Ошибка 2', () => {});
Лучше:
test('emailFormat', 'Неверный email', () => {});
test('emailRequired', 'Email обязателен', () => {});
Плохо:
test('data', () => {
riskyFunction();
});
Лучше:
test('data', () => {
try {
riskyFunction();
} catch {
throw new Error('Ошибка обработки данных');
}
});
Плохо:
const result = suite(data);
console.log(result.isValid());
При наличии async-тестов результат может быть ещё не готов.
Правильно:
if (!result.isPending()) {
console.log(result.isValid());
}
import {
create,
test,
enforce,
warn,
group
} from 'vest';
const suite = create(async data => {
group('auth', () => {
test('email', 'Email обязателен', () => {
enforce(data.email).isNotBlank();
});
test('email_format', 'Неверный email', () => {
enforce(data.email).matches(/^\S+@\S+\.\S+$/);
});
test('password', 'Минимум 8 символов', () => {
enforce(data.password).longerThanOrEquals(8);
});
test('password_strength', 'Слабый пароль', () => {
warn();
enforce(data.password).longerThanOrEquals(12);
});
test('email_unique', async () => {
const exists = await api.emailExists(data.email);
enforce(exists).isFalsy();
}, 'Email уже используется');
});
});
const result = suite({
email: 'wrong',
password: '123'
});
console.log(result.getErrors());
console.log(result.getWarnings());
console.log(result.hasErrorsByGroup('auth'));