@astral/validations
v4.20.0
Published
Библиотека для валидаций в функциональном стиле.
Downloads
725
Readme
@astral/validations
Библиотека для валидаций в функциональном стиле.
Особенности:
- ⚡️️️️ Ориентирована на специфику frontend-приложений
- ⚡️️️️ Функциональный стиль
- ⚡️️️️ Валидация по схеме, а также использование правил валидации вне контекста схемы
- ⚡️️️️ Оптимизирована для валидации форм (есть поддержка react-hook-form)
- ⚡️️️️ Полноценная поддержка tree shaking
- ⚡️️️️ Простота создания кастомных правил валидации
Table of contents
- Installation
- Basic usage
- Guards
- Custom rules
- Common
- Async
- Integrations
- Guides
- Migration Guide
Installation
npm i --save @astral/validations
yarn add @astral/validations
Basic usage
Валидация объекта с вложенным массивом.
Codesandbox
import {
object,
array,
arrayItem,
string,
optional,
min,
number,
toPrettyError,
} from '@astral/validations';
type Permission = {
id: number;
description: string;
};
type User = {
name: string;
surname?: string;
info: {
permissions: Permission[];
};
};
const validate = object<User>({
name: string(),
surname: optional(string()),
info: object<User['info']>({
permissions: array(
arrayItem(
object<Permission>({
id: number(),
description: string(min(2)),
}),
),
),
}),
});
// undefined
validate({
name: 'Vasya',
info: {
permissions: [{ id: 1, description: 'my permission' }],
},
});
// { info: { permissions: [{ description: 'Обязательно' }] } }
toPrettyError(
validate({
name: 'Vasya',
info: {
permissions: [{ id: 1 }],
},
})
);
Валидация отдельных value
import { number, string, max } from '@astral/validations';
// undefined
number()(22)
// { message: 'Обязательно' }
string()('')
// { message: 'Макс. символов: 5' }
string(max(5))('123456')
Guards
Guard - правило, выполняющее проверку на тип данных. Guard должен быть предикатом для любой валидации.
Каждый guard:
- Проверяет значение на required (для каждого типа данных своя проверка)
- Имеет метод
define
, позволяющий переопределять стандартные параметры guard
number
- Возвращает ошибку если:
- Тип value не number
- Value является NaN
- Value является Infinity
- Проверяет value на required
- Выполняет композицию правил, переданных в параметры
import { number, min, max } from '@astral/validations';
const validate = number(min(1), max(22));
// undefined
validate(20)
// { message: 'Не число' }
validate('string')
// { message: 'Некорректное число' }
validate(NaN)
// { message: 'Бесконечное число' }
validate(Infinity)
// { message: 'Обязательно' }
validate(undefined)
// { message: 'Обязательно' }
validate(null)
min number
Позволяет указать ограничение на минимальное число.
import { number, min } from '@astral/validations';
const validate = number(min(1));
// undefined
validate(20)
// undefined
validate(1)
// { message: 'Не меньше: 1' }
validate(0)
max number
Позволяет указать ограничение на максимальное число.
import { number, max } from '@astral/validations';
const validate = number(max(4));
// undefined
validate(4)
// undefined
validate(1)
// { message: 'Не больше: 4' }
validate(10)
integer
Проверяет является ли значение целым числом.
import { number, integer } from '@astral/validations';
const validate = number(integer(5));
// undefined
validate(5)
// undefined
validate(7)
// { message: 'Только целые числа' }
validate(3.14)
positiveNumber
Проверяет является ли значение положительным числом.
import { number, positiveNumber } from '@astral/validations';
const validate = number(positiveNumber(3));
// undefined
validate(3)
// { message: 'Только положительное числа' }
validate(0)
// { message: 'Только положительное числа' }
validate(-1)
string
- Возвращает ошибку если:
- Тип value не string
- Проверяет value на required
- Выполняет композицию правил, переданных в параметры
import { string, min, onlyNumber } from '@astral/validations';
const validate = string(min(1), onlyNumber());
// undefined
validate('name')
// { message: 'Не является строкой' }
validate(20)
// { message: 'Обязательно' }
validate('')
// { message: 'Обязательно' }
validate(undefined)
// { message: 'Обязательно' }
validate(null)
min string
Позволяет указать ограничение на минимальное количество символов в строке.
import { string, min } from '@astral/validations';
const validate = string(min(2));
// undefined
validate('vasya')
// undefined
validate('va')
// { message: 'Мин. символов: 2' }
validate('v')
max string
Позволяет указать ограничение на максимальное количество символов в строке.
import { string, max } from '@astral/validations';
const validate = string(max(6));
// undefined
validate('hello')
// undefined
validate('va')
// { message: 'Макс. символов: 6' }
validate('long string')
Проверяет валиден ли email. Работает с русскими доменами и punycode
import { string, email } from '@astral/validations';
const validate = string(email());
// undefined
validate('[email protected]');
// { message: 'Некорректный E-mail' }
validate('example.ru');
//Пользовательское сообщение для ошибки с максимальным количеством символов
const validateEmail = email({ invalidLengthMessage: 'слишком длинный email' });
// { message: 'слишком длинный email' }
validateEmail('longlonglong.......')
//Проверка email с разрешенной кирилицей и punycode
const validateCyrillicEmail = email({ allowCyrillic: true });
// undefined
validateCyrillicEmail('test@домен.рф')
// undefined
validateCyrillicEmail('[email protected]')
guid
Проверяет валиден ли GUID.
import { string, guid } from '@astral/validations';
const validate = string(guid());
// undefined
validate('C56A4180-65AA-42EC-A945-5FD21DEC0538');
// { message: 'Некорректный GUID' }
validate('x56a4180-h5aa-42ec-a945-5fd21dec0538');
length
Проверяет значение на соответствие длине.
import { string, length } from '@astral/validations';
const validate = string(length(5));
// undefined
validate('aaaaa');
// { message: 'Кол-во символов должно быть: 5' }
validate('abc');
pattern
Проверяет строку на соответствие регулярному выражению.
import { string, pattern } from '@astral/validations';
const validate = string(
pattern(/word/g, { message: 'Должен быть word' })
);
// undefined
validate('word')
// { message: 'Должен быть word' }
validate('vasya')
onlyNumber
Проверяет на наличие только чисел в строке
import { string, onlyNumber } from '@astral/validations';
const validate = string(onlyNumber());
// undefined
validate('12345')
// { message: 'Строка должна содержать только числа' }
validate('a12345')
validate('1.2345')
validate('-1.2345')
containsNumbers
Проверяет на наличие чисел в строке
import { string, containsNumbers } from '@astral/validations';
const validate = string(containsNumbers());
// undefined
validate('test12345')
// { message: 'Строка должна содержать числа' }
validate('test')
containsDifferentCases
Проверяет на наличие в строке символов разных регистров
import { string, containsDifferentCases } from '@astral/validations';
const validate = string(containsDifferentCases());
// undefined
validate('testTEST')
validate('тестТЕСТ')
// { message: 'Строка должна содержать символы разного регистра' }
validate('test')
validate('ТЕСТ')
containsPunctuationMarks
Проверяет на наличие в строке знаков пунктуации !$%&’()+,-./:;<=>?@[]^_{|}”
import { string, containsPunctuationMarks } from '@astral/validations';
const validate = string(containsPunctuationMarks());
// undefined
validate('test?')
// { message: 'Строка должна содержать знаки пунктуации' }
validate('test')
snils
Проверяет валиден ли СНИЛС
import { string, snils } from '@astral/validations';
const validate = string(snils());
// undefined
validate('15657325992')
// { message: 'Некорректный СНИЛС' }
validate('95145370511')
validate('156-573-259 92')
:information_source: Поддерживает exclude
textField
Правило реализует дефолтные ограничения для произвольных текстовых полей форм Астрал-Софт.
import { string, textField } from '@astral/validations';
const validate = string(textField());
// undefined
validate('!@#$%^&*()-_=+|[]{};:",.<>/?')
validate('абвАБВ abcABC')
// { message: 'Содержит запрещённые символы' }
validate('😀')
validate('∑')
validate('٩(◕‿◕。)۶')
mobilePhone
- Проверяет валиден ли мобильный телефон
- Валидный телефон начинается с "79" и не содержит символов, кроме цифр.
import { string, mobilePhone } from '@astral/validations';
const validate = string(mobilePhone());
// undefined
validate('79999999999')
// { message: 'Некорректный номер телефона' }
validate('7 (999) 99-99-999')
validate('89999999999')
validate('+79999999999')
:information_source: Поддерживает exclude
innUL
Проверяет валиден ли ИНН ЮЛ
import { string, innUL } from '@astral/validations';
const validate = string(innUL());
// undefined
validate('7728168971')
// { message: 'Некорректный ИНН ЮЛ' }
validate('0000000000')
validate('384212952720')
validate('7728168911')
:information_source: Поддерживает exclude
innIP
Проверяет валиден ли ИНН ИП
import { string, innIP } from '@astral/validations';
const validate = string(innIP());
// undefined
validate('384212952720')
// { message: 'Некорректный ИНН ИП' }
validate('3842129527')
validate('384212952a20')
validate('+384212952720')
:information_source: Поддерживает exclude
innFL
Проверяет валиден ли ИНН ФЛ
import { string, innFL } from '@astral/validations';
const validate = string(innFL());
// undefined
validate('384212952720')
validate('000000000000')
// { message: 'Некорректный ИНН ФЛ' }
validate('3842129527')
validate('384212952a20')
validate('+384212952720')
:information_source: Поддерживает exclude
innTwelveSymbols
Проверяет валиден ли ИНН из 12 символов
import { string, innTwelveSymbols } from '@astral/validations';
const validate = string(innTwelveSymbols());
// undefined
validate('384212952720')
// { message: 'Некорректный ИНН' }
validate('3842129527')
validate('384212952a20')
validate('+384212952720')
:information_source: Поддерживает exclude
kpp
Проверяет валиден ли КПП
import { string, kpp } from '@astral/validations';
const validate = string(kpp());
// undefined
validate('770201001');
// { message: 'Некорректный КПП' }
validate('123123')
validate('00000000')
:information_source: Поддерживает exclude
ogrnIP
Проверяет валиден ли ОГРН ИП
import { string, ogrnIP } from '@astral/validations';
const validate = string(ogrnIP());
// undefined
validate('8104338364837')
// { message: 'Некорректный ОГРН ИП' }
validate('1175958036814')
validate('1175958000004')
validate('1-22-33-44-5555555-6')
:information_source: Поддерживает exclude
ogrnUL
Проверяет валиден ли ОГРН ЮЛ
import { string, ogrnUL } from '@astral/validations';
const validate = string(ogrnUL());
// undefined
validate('1214000000092')
// { message: 'Некорректный ОГРН ЮЛ' }
validate('1175958036814')
validate('1175958000004')
validate('1-22-33-5555555-6')
:information_source: Поддерживает exclude
personName
Проверяет валидно ли имя
Требования на реализацию
import { string, personName } from '@astral/validations';
const validate = string(personName());
// undefined
validate('Иван');
validate('иван');
// { message: 'Проверьте имя' }
validate('');
validate('Иван--Иван');
personSurname
Проверяет валидно ли фамилия
Требования на реализацию
import { string, personSurname } from '@astral/validations';
const validate = string(personSurname());
// undefined
validate('Иванов');
validate('иванов');
// { message: 'Проверьте фамилию' }
validate('');
validate('Иванов--иванов');
personPatronymic
Проверяет валидно ли отчество
Требования на реализацию
import { string, personPatronymic } from '@astral/validations';
const validate = string(personPatronymic());
// undefined
validate('Иванович');
validate('иванович');
// { message: 'Проверьте отчество' }
validate('');
validate('Иванович--Иванович');
passportSeries
Проверяет валидна ли серия паспорта
Требования на реализацию
import { string, passportSeries } from '@astral/validations';
const validate = string(passportSeries());
// undefined
validate('9217');
// { message: 'Проверьте серию' }
validate('0017');
// { message: 'Длина поля должна быть равна 4 символам' }
validate('917');
// { message: 'Только цифры' }
validate('91а7');
passportNumber
Проверяет валиден ли номер паспорта
Требования на реализацию
import { string, passportNumber } from '@astral/validations';
const validate = string(passportNumber());
// undefined
validate('704564');
// { message: 'Проверьте номер' }
validate('000100');
// { message: 'Длина поля должна быть равна 6 символам' }
validate('7045');
// { message: 'Только цифры' }
validate('70а5');
passportCode
Проверяет валиден ли код паспорта
Требования на реализацию
import { string, passportCode } from '@astral/validations';
const validate = string(passportCode());
// undefined
validate('123256');
// { message: 'Проверьте код' }
validate('000-456');
// { message: 'Длина поля должна быть равна 6 символам' }
validate('1234');
// { message: 'Только цифры' }
validate('1а3');
date
- Возвращает ошибку если:
- Тип value не является объектом Date
- Date является invalid date
- Проверяет value на required
- Выполняет композицию правил, переданных в параметры
import { date } from '@astral/validations';
const validate = date();
// undefined
validate(new Date());
// { message: 'Некорректная дата' }
validate(new Date('22.22.2022'));
// { message: 'Не дата' }
validate('12.12.2022');
// { message: 'Обязательно' }
validate(undefined);
min date
Позволяет указать минимальную дату. При сверке дат игнорируется время, которое может быть отличное от 00:00:00 в объекте Date.
import { date, min } from '@astral/validations';
const validate = date(
min(new Date('12-12-2022'), { message: 'Начиная с 12 января 2022 года' }),
);
// { message: 'Начиная с 12 января 2022 года' }
validate(new Date('12-11-2022'));
// undefined
validate(new Date('12-14-2022'));
max date
Позволяет указать максимальную дату. При сверке дат игнорируется время, которое может быть отличное от 00:00:00 в объекте Date.
import { date, max } from '@astral/validations';
const validate = date(
max(new Date('12-12-2022'), { message: 'Не позднее 12 января 2022 года' }),
);
// { message: 'Не позднее 12 января 2022 года' }
validate(new Date('15-11-2022'));
// undefined
validate(new Date('02-01-2021'));
#min years old date
Принимает возраст и вычитает переданное количество лет из текущей даты. Позволяет кастомизировать текст ошибки.
import { date, minYearsOld } from '@astral/validations';
const validate = date(
minYearsOld(18, {
customErrorMessage:
'Только совершеннолетние могут воспользоваться данной услугой',
}),
);
// { message: 'Только совершеннолетние могут воспользоваться данной услугой' }
validate(new Date('15.11.2022'));
// undefined
validate(new Date('10.10.2005'));
rangeDate
- Проверяет даты интервала на обязательность заполнения, валидность значений и хронологический порядок
- Выполняет композицию правил, переданных в параметры
import { rangeDate } from '@astral/validations';
const validate = rangeDate();
// { message: 'Укажите период' }
validate({});
// { message: 'Укажите дату окончания' }
validate({
start: new Date('2024.09.05'),
})
// { message: 'Укажите дату начала' }
validate({
end: new Date('2024.09.24'),
})
// { message: 'Дата начала некорректная' }
validate({
start: new Date('2024.99.99'),
end: new Date('2024.09.24'),
})
// { message: 'Дата окончания некорректная' }
validate({
start: new Date('2024.09.05'),
end: new Date('2024.99.99'),
})
// { message: 'Дата окончания не может быть раньше даты начала' }
validate({
start: new Date('2024.09.24'),
end: new Date('2024.09.05'),
})
// undefined
validate({
start: new Date('2024.09.05'),
end: new Date('2024.09.24'),
});
const customValidate = (...rules: DateRangeRule[]) => dateRange(...rules).define({
required: { start: false, end: false },
message: {
startRequired: 'Укажите начало периода'
}
})
// undefined
customValidate({
start: new Date('2024.09.05'),
});
// { message: 'Укажите начало периода' }
validate({
end: new Date('2024.09.05'),
})
rangeDateInterval
Позволяет ограничить интервал на конкретное значение. По умолчанию значение интервала задаётся в днях, но с помощью опции unit можно задать значение в месяцах или годах.
import { rangeDateInterval } from '@astral/validations';
const validate = rangeDate(rangeDateInterval({ limit: 14 }));
// { message: 'Период не может превышать 14 дней' }
validate({
start: new Date('2024.09.05'),
end: new Date('2024.09.24'),
});
// undefined
validate({
start: new Date('2024.09.05'),
end: new Date('2024.09.12'),
});
const validateMonthInterval = rangeDate(rangeDateInterval({ limit: 2, unit: 'month' }));
// { message: 'Период не может превышать 2 месяца' }
validateMonthInterval({
start: new Date('2024.09.05'),
end: new Date('2024.12.24'),
});
// undefined
validateMonthInterval({
start: new Date('2024.09.05'),
end: new Date('2024.09.12'),
});
rangeDateMinMax
Проверяет даты на минимальное и максимальное допустимое значение
import { rangeDateMinMax } from '@astral/validations';
const validate = rangeDate(rangeDateMinMax({ start: { min: { limit: new Date('2024.09.05') }}, end: { max: { limit: new Date('2024.09.15') } } }))
// { message: 'Дата начала должна быть позже 05.09.2024' }
validate({
start: new Date('2024.09.01'),
end: new Date('2024.09.10'),
});
// { message: 'Дата окончания должна быть раньше 15.09.2024}' }
validate({
start: new Date('2024.09.06'),
end: new Date('2024.09.20'),
});
// undefined
validate({
start: new Date('2024.09.06'),
end: new Date('2024.09.14'),
});
rangeDateNotEqual
Проверяет даты интервала на совпадение даты начала и окончания
import { rangeDateNotEqual } from '@astral/validations';
const validate = rangeDate(
rangeDateNotEqual(),
);
// { message: 'Даты начала и окончания не могут совпадать' }
validate({
start: new Date('2024.09.05'),
end: new Date('2024.09.05'),
});
// undefined
validate({
start: new Date('2024.09.05'),
end: new Date('2024.09.06'),
});
boolean
- Возвращает ошибку если:
- Тип value не boolean
- Проверяет value на required
- Выполняет композицию правил, переданных в параметры
import { boolean } from '@astral/validations';
const validate = boolean();
// undefined
validate(true)
// { message: 'Не boolean' }
validate('string')
// { message: 'Обязательно' }
validate(false)
// { message: 'Обязательно' }
validate(undefined)
// { message: 'Обязательно' }
validate(null)
object
- Позволяет валидировать объект по схеме
- Возвращает ошибку если:
- Value не является простым объектом
- Свойства не соответсвуют переданной схеме валидации
- Возвращаем объект ошибок, соответсвующих ошибкам для свойств объекта
- Требует схему для валидации, свойства которой должны соответствовать валидируемому values
import {
object,
array,
arrayItem,
string,
optional,
min,
number,
toPrettyError
} from '@astral/validations';
type User = {
name: string;
surname?: string;
info: {
permissions: Permission[];
};
};
const validate = object<User>({
name: string(),
surname: optional(string()),
info: object<User['info']>({
permissions: array(
arrayItem(
object<Permission>({
id: number(),
description: string(min(2)),
}),
),
),
}),
});
// undefined
validate({
name: 'Vasya',
info: {
permissions: [{ id: 1, description: 'my permission' }],
},
});
// { info: { permissions: [{ description: 'Обязательно' }] } }
toPrettyError(
validate({
name: 'Vasya',
info: {
permissions: [{ id: 1 }],
},
})
);
partial
Позволяет сделать все поля объекта optional.
import { partial, object, string, toPrettyError } from '@astral/validations';
type Values = {
name: string;
surname: string;
};
const validateRequired = object<Values>({
name: string(),
surname: string()
})
// { name: 'Обязательно' }
toPrettyError(
validateRequired({})
);
const validatePartial = partial(
object<Values>({
name: string(),
surname: string()
})
);
// undefined
validatePartial({});
deepPartial
Позволяет сделать гулбокий partial для свойсв всех объектов в схеме, включая объекты в массиве.
import {
object,
array,
arrayItem,
string,
deepPartial,
min,
number
} from '@astral/validations';
type Permission = {
id: number;
description: string;
};
type User = {
name: string;
surname?: string;
info: {
permissions: Permission[];
};
};
const validate = deepPartial(
object<User>({
name: string(),
surname: optional(string()),
info: object<User['info']>({
permissions: array(
arrayItem(
object<Permission>({
id: number(),
description: string(min(2)),
}),
),
),
}),
})
);
// undefined
validate({
info: {
permissions: [{}]
},
});
array
- Позволяет валидировать array
- Возвращает ошибку если:
- Value не является array
- Выполняет композицию правил, переданных в параметры
import {
array,
arrayItem,
min,
toPrettyError
} from '@astral/validations';
type User = {
name: string;
surname?: string;
};
const validate = array(
min(1),
arrayItem(
object<User>({
name: string(),
surname: optional(string()),
}),
),
);
// undefined
validate([{ name: 'Vasya' }]);
// { message: 'Не меньше: 1' }
validate([]);
// [{ name: 'Не является строкой' }]
toPrettyError(
validate([{ name: 22 }])
);
arrayItem
Применяет переданные правила валидации к каждому элементу массива.
import { array, arrayItem, object, string, optional, toPrettyError } from '@astral/validations';
type User = {
name: string;
surname?: string;
};
const validate = array(
arrayItem(
object<User>({
name: string(),
surname: optional(string()),
}),
),
);
// undefined
validate([{ name: 'Vasya' }]);
// { cause: { errorArray: [{ name: { message: 'Не является строкой' } }] } }
toPrettyError(
validate([{ name: 22 }])
);
import { array, arrayItem, string, min, toPrettyError } from '@astral/validations';
const validate = array(arrayItem(string(min(3))));
// [undefined, 'Мин. символов: 3']
toPrettyError(
validate(['vasya', 'ma'])
);
min array
Позволяет указать ограничение на минимальное количество элементов в массиве.
import { array, min } from '@astral/validations';
const validate = array(min(1));
// { message: 'Не меньше: 1' }
validate([]);
// undefined
validate([1, 2]);
max array
Позволяет указать ограничение на максимальное количество элементов в массиве.
import { array, max } from '@astral/validations';
const validate = array(max(3));
// { message: 'Не больше: 3' }
validate([1,2,3,4]);
// undefined
validate([1, 2]);
any
Позволяет выключить любые проверки и делать композицию для правил, валидирующих любые значения.
type Values = { name: string; isAgree: boolean };
const validate = object<Values>({
name: when({
is: (_, ctx) => Boolean(ctx.values?.isAgree),
then: string(),
otherwise: any(),
}),
isAgree: optional(boolean()),
});
// undefined
validate({ isAgree: false, name: '' });
// { name: 'Обязательно' }
toPrettyError(
validate({ isAgree: true, name: '' })
);
const validate = any(transform((value) => new Date(value), date()));
// undefined
validate('12.22.2022');
// invalid date error
validate('13.22.2022');
Define. Переопределение дефолтных параметров guard
Каждый guard позволяет переопределить дефолтные параметры:
- Сообщение об ошибке типа
- Сообщение об ошибке required
- Уникальные для каждого guard параметры
import { string } from '@astral/validations';
const validateCustomString = string().define({
typeErrorMessage: 'Только строка',
requiredErrorMessage: 'Не может быть пустым',
});
// { message: 'Не может быть пустым' }
validateCustomString(undefined);
// { message: 'Только строка' }
validateCustomString(20);
Custom rules
Каждый guard поддерживает кастомные правила.
Базовый пример
import { string, object, toPrettyError } from '@astral/validations';
type Values = {
name: string;
nickname: string;
};
const validate = object<Values>({
name: string(),
nickname: string((value, ctx) => {
if (value.includes('_')) {
return ctx.createError({
message: 'Символ "_" запрещен',
code: 'nickname-symbol',
});
}
return undefined;
}),
});
// { nickname: 'Символ "_" запрещен' }
toPrettyError(
validate({ name: 'Vasya', nickname: 'va_sya' })
);
Связанные поля
В ctx.values
находится value, принятое последним object.
import { object, string, toPrettyError } from '@astral/validations';
type Values = {
password: string;
repeatPassword: string;
};
const validate = object<Values>({
password: string(min(9)),
repeatPassword: string(min(9), (value, ctx) => {
if (value !== ctx.values?.password) {
return ctx.createError({
message: 'Пароли не совпадают',
code: 'repeat-password',
});
}
return undefined;
}),
});
// { repeatPassword: 'Пароли не совпадают' }
toPrettyError(
validate({ password: 'qywerty123', repeatPassword: 'qywerty1234' })
);
Доступ к высокоуровневым values (ctx.global.values
)
В ctx.global.values
находится values, полученное самым первым guard.
import { object, string, boolean, optional } from '@astral/validations';
type Values = {
isAgree: boolean;
info: {
name: string
}
};
const validate = object<Values>({
isAgree: optional(boolean()),
info: object<Values['info']>({
name: when({
is: (_, ctx) => Boolean(ctx.global.values?.isAgree),
then: string(),
otherwise: any(),
}),
})
});
Переиспользуемое правило
import { createRule, string } from '@astral/validations';
type Params = {
message?: string;
};
const includesWorld = <TValues>(params: Params) =>
createRule<string, TValues>((value, ctx) => {
if (value.includes('world')) {
return undefined;
}
return ctx.createError({
message: params?.message || 'Должен содержать "world"',
code: 'includes-word',
});
});
const validate = string(includesWorld());
// undefined
validate('Hello world');
// { message: 'Должен содержать "world"' }
validate('Hello');
// { message: 'Должен содержать "world"' }
includesWorld()('Hello')
Кастомная условная валидация
Для условной валидации рекомендуется использовать when, но также доступна возможность реализации кастомной условной валидации.
import { object, string, boolean, optional } from '@astral/validations';
type Values = {
isAgree: boolean;
info: {
name: string
}
};
const validate = object<Values>({
isAgree: optional(boolean()),
info: object<Values['info']>({
name: (value, ctx) => {
if(ctx.global.values?.isAgree) {
return string();
}
return any();
}
})
});
Common
optional
Выключает дефолтную проверку на required в guard.
import { optional, object, string, boolean, array } from '@astral/validations';
type Values = {
name: string;
surname?: string;
permissions?: number[];
isAuth?: boolean;
};
const validate = object<Values>({
name: string(),
surname: optional(string()),
permissions: optional(array(string())),
isAuth: optional(boolean()),
})
// undefined
validate({
name: 'Vasya',
surname: '',
isAuth: false,
});
Позволяет делать optional вложенные правила:
type Values = { name: string | number; isAgree: boolean };
const validate = object<Values>({
name: optional(
when({
is: (_, ctx) => Boolean(ctx.values?.isAgree),
then: string(),
otherwise: number(),
})
),
isAgree: optional(boolean()),
});
// undefined
validate({ isAgree: false, name: undefined });
when. Условная валидация
Позволяет определять условные валидации.
type Values = { name: string; isAgree: boolean };
const validate = object<Values>({
name: when({
is: (_, ctx) => Boolean(ctx.values?.isAgree),
then: string(),
otherwise: any(),
}),
isAgree: optional(boolean()),
});
// undefined
validate({ isAgree: false, name: '' });
// { name: 'Обязательно' }
toPrettyError(
validate({ isAgree: true, name: '' })
);
When для ветки объекта:
type ValuesInfo = { surname: string };
type Values = {
name: string;
info?: ValuesInfo;
};
const validate = object<Values>({
name: string(),
info: when({
is: (_, ctx) => ctx.values?.name === 'Vasya',
then: object<ValuesInfo>({ surname: string() }),
otherwise: any(),
}),
});
// { info: 'Обязательно' }
toPrettyError(
validate({ name: 'Vasya' })
);
// undefined
validate({ name: 'Kolya' });
enabled. Условная валидация
Позволяет определять условные валидации без выбора альтернативной схемы. Является упрощением when с otherwise = any().
type Values = { name: string; isAgree: boolean };
const validate = object<Values>({
name: enabled({
is: (_, ctx) => Boolean(ctx.values?.isAgree),
then: string(),
}),
isAgree: optional(boolean()),
});
// undefined
validate({ isAgree: false, name: '' });
// { name: 'Обязательно' }
toPrettyError(
validate({ isAgree: true, name: '' })
);
Enabled для ветки объекта:
type ValuesInfo = { surname: string };
type Values = {
name: string;
info?: ValuesInfo;
};
const validate = object<Values>({
name: string(),
info: enabled({
is: (_, ctx) => ctx.values?.name === 'Vasya',
then: object<ValuesInfo>({ surname: string() }),
}),
});
// { info: 'Обязательно' }
toPrettyError(
validate({ name: 'Vasya' })
);
// undefined
validate({ name: 'Kolya' });
transform
Позволяет изменять value в цепочке композиции.
import { transform, date, min } from '@astral/validations';
const validate = string(
transform((value) => new Date(value), date(min(new Date()))),
);
// { message: 'Некорректная дата' }
validate('22.22.2022');
// undefined
validate('12.12.2022');
or
Выполняет переданные правила аналогично оператору ||. Если одно из правил не завершилось ошибкой, то or вернет undefined. Если все переданные правила завершились с ошибкой, то вернется ошибка из последнего правила
import { or, array, string, number } from '@astral/validations';
const validate = or(string(), array(), number());
// undefined
validate('string')
// undefined
validate([])
// undefined
validate(20)
// { message: 'Не число' }
validate(new Date())
Async
Пакет поддерживает асинхронную валидацию.
Guard, поддерживающие асинхронную валидацию имеют постфиксы async
:
objectAsync
stringAsync
optionalAsync
Пример:
type Values = {
nickname: string;
phone: string;
};
const validate = objectAsync<Values>({
phone: string(),
nickname: stringAsync(min(3), async (value, ctx) => {
const nicknameIsAvailable = await checkNickname(value);
if (nicknameIsAvailable) {
return undefined;
}
return ctx.createError({
code: 'nickname-available',
message: 'Nickname занят',
});
}),
fullName: optionalAsync(stringAsync(async (value, ctx) => {
const nicknameIsAvailable = await checkNickname(value);
if (nicknameIsAvailable) {
return undefined;
}
return ctx.createError({
code: 'nickname-available',
message: 'Nickname занят',
});
})),
});
const result = await validate({ phone: '79308999999', nickname: 'Vasya', fullName: '' });
// { nickname: 'Nickname занят' }
toPrettyError(result);
Integrations
react-hook-form
Для интеграции с react-hook-form необходимо использовать пакет @astral/validations-react-hook-form-resolver
.
Codesandbox
Basic usage
import { object, string, optional } from '@astral/validations';
import { resolver } from '@astral/validations-react-hook-form-resolver';
import { useForm } from 'react-hook-form';
type Values = {
name: string;
info: { description?: string }
};
const validationSchema = object<Values>({
name: string(),
info: object<Values['info']>({
description: optional(string()),
}),
});
const Form = () => {
const { register, handleSubmit, formState } = useForm<Values>({
resolver: resolver<Values>(validationSchema),
});
return (
<form onSubmit={handleSubmit(() => {})}>
<input {...register('name')} />
{formState.errors.name && (
<p>{formState.errors.name.message}</p>
)}
<input {...register('info.description')} />
{formState.errors.info?.description && (
<p>{formState.errors.info.description.message}</p>
)}
<button type="submit">submit</button>
</form>
);
};
Переиспользуемый useForm
import { ObjectGuard, object, optional, string } from '@astral/validations';
import { resolver } from '@astral/validations-react-hook-form-resolver';
import {
FieldValues,
UseFormReturn,
UseFormProps as UseReactHookFormProps,
useForm as useReactHookForm,
} from 'react-hook-form';
type UseFormProps<TFieldValues extends FieldValues = FieldValues> = Omit<
UseReactHookFormProps<TFieldValues>,
'resolver'
> & {
validationSchema?: ObjectGuard<TFieldValues, TFieldValues>;
};
const useForm = <TFieldValues extends FieldValues = FieldValues>({
validationSchema,
defaultValues,
...params
}: UseFormProps<TFieldValues>): UseFormReturn<TFieldValues> =>
useReactHookForm<TFieldValues>({
...params,
defaultValues,
resolver: validationSchema && resolver(validationSchema),
});
type Values = {
name: string;
info: { description?: string };
};
const validationSchema = object<Values>({
name: string(),
info: object<Values['info']>({
description: optional(string()),
}),
});
const Form = () => {
const { register, handleSubmit, formState } = useForm<Values>({
validationSchema,
});
return (
<form onSubmit={handleSubmit(() => {})}>
<input {...register('name')} />
{formState.errors.name && <p>{formState.errors.name.message}</p>}
<input {...register('info.description')} />
{formState.errors.info?.description && (
<p>{formState.errors.info.description.message}</p>
)}
<button type="submit">submit</button>
</form>
);
};
Guides
Переиспользование объектов схемы
type Address = {
street: string;
};
const address = object<Address>({ street: string() });
type Organization = {
address: Address;
};
const organization = object<Organization>({ address });
type Values = {
name: string;
org: Organization;
};
const validateValues = object<Values>({
name: string(),
org: organization,
});
Переиспользование объектов схемы, с условной валидацией и зависимыми полями
type RusOrganization = {
inn: string;
isIP: boolean;
};
type EngOrganization = {
name: string;
};
type Values = {
isRus: boolean;
org: { data: RusOrganization | EngOrganization };
};
const rusOrganization = object<RusOrganization>({
inn: string(
when({
is: (_, ctx) => Boolean((ctx.global.values as Values)?.isRus),
then: rusOrganization,
otherwise: engOrganization,
}),
),
isIP: optional(boolean()),
});
const engOrganization = object<EngOrganization, Values>({ name: string() });
const organization = when<Values>({
is: (_, ctx) => Boolean((ctx.global.values as Values)?.isRus),
then: rusOrganization,
otherwise: engOrganization,
});
const validate = object<Values>({
isRus: optional(boolean()),
org: organization,
});
Error message customization
Сообщения об ошибках по умолчанию могут быть заменены на пользовательские.
Для этого необходимо использовать параметры message
или getMessage
у валидационных методов:
//getMessage
const validateMin = number(min(10, {
getMessage: (threshold, value, ctx) => {
return `Слишком мало, минимум ${threshold}`
}
}));
// { message: 'Слишком мало, минимум 10' }
validateMin(5);
//message
const validateKPP = string(kpp({ message: 'Что-то не так с кодом КПП' }));
// { message: 'Что-то не так с кодом КПП' }
validateKPP('123123');
Exclusion managing
Метод exclude
предоставляет возможность обхода валидации для конкретного значения.
Если функция вернет true
,
текущее значение не будет провалидировано, метод валидации вернет undefined
.
Пример реализации:
//значение для обхода валидации (исключение)
const excludeValue = '0101010101';
//функция для обработки исключения
const isExclude = (value: string) => {
const excluded: string[] = [excludeValue];
return excluded.includes(value);
};
const validate = string(kpp({ exclude: isExclude }));
// undefined (значение не будет провалидировано)
validate(excludeValue);
Migration guide
v3 -> v4
object
Generic object guard стал принимать один параметр - валидируемое значение.
Типизация guard и rules
Generics правил и guards стали принимать тип для ctx.values
вместо ctx.global.values
.
ctx.global.values
ctx.global.values
стал unknown
. Для использования необходимо вручную уточнять тип. Пример.