4.14.0 • Published 5 days ago

@astral/validations v4.14.0

Weekly downloads
-
License
MIT
Repository
github
Last release
5 days ago

@astral/validations

Библиотека для валидаций в функциональном стиле.

Особенности:

  • ⚡️️️️ Ориентирована на специфику frontend-приложений
  • ⚡️️️️ Функциональный стиль
  • ⚡️️️️ Валидация по схеме, а также использование правил валидации вне контекста схемы
  • ⚡️️️️ Оптимизирована для валидации форм (есть поддержка react-hook-form)
  • ⚡️️️️ Полноценная поддержка tree shaking
  • ⚡️️️️ Простота создания кастомных правил валидации

Table of contents


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

Проверяет валиден ли email. Не работает с русскими доменами

import { string, email } from '@astral/validations';

const validate = string(email());

// undefined
validate('example@mail.ru');


// { message: 'Некорректный E-mail' }
validate('example.ru');

//Пользовательское сообщение для ошибки с максимальным количеством символов
const validateEmail = email({ invalidLengthMessage: 'слишком длинный email' });

// { message: 'слишком длинный email' }
validateEmail('longlonglong.......')

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

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. Для использования необходимо вручную уточнять тип. Пример.

4.14.0

5 days ago

4.13.3

2 months ago

4.13.2

4 months ago

4.13.1

5 months ago

4.13.0

6 months ago

4.12.0

6 months ago

4.11.0

7 months ago

4.9.0

8 months ago

4.9.2

7 months ago

4.9.1

7 months ago

3.2.2

10 months ago

4.0.0

9 months ago

4.8.1

8 months ago

4.8.0

8 months ago

4.7.0

8 months ago

4.6.1

8 months ago

4.6.0

8 months ago

4.5.0

8 months ago

4.4.0

9 months ago

4.10.1

7 months ago

4.10.0

7 months ago

4.3.0

9 months ago

4.2.0

9 months ago

3.3.0

10 months ago

4.1.0

9 months ago

3.2.1

11 months ago

3.2.0

11 months ago

3.1.1

11 months ago

3.1.0

11 months ago

2.23.1

1 year ago

2.27.0

12 months ago

2.22.0

1 year ago

3.0.0-beta.1

12 months ago

3.0.0-beta.0

12 months ago

3.0.0-beta.3

12 months ago

3.0.0-beta.4

12 months ago

2.26.0

12 months ago

2.21.0

1 year ago

2.25.3

12 months ago

2.25.0

12 months ago

2.25.2

12 months ago

2.25.1

12 months ago

2.20.2

1 year ago

2.20.3

1 year ago

2.20.0

1 year ago

2.20.1

1 year ago

2.24.0

12 months ago

2.23.0

1 year ago

2.19.0

1 year ago

2.18.1

1 year ago

2.18.0

1 year ago

2.17.0

1 year ago

2.11.0

1 year ago

2.11.1

1 year ago

2.8.0

1 year ago

2.15.2

1 year ago

2.15.0

1 year ago

2.15.1

1 year ago

2.10.2

1 year ago

2.10.0

1 year ago

2.7.0

1 year ago

2.7.2

1 year ago

2.7.1

1 year ago

2.10.5

1 year ago

2.10.3

1 year ago

2.14.0

1 year ago

2.10.4

1 year ago

2.7.4

1 year ago

2.7.3

1 year ago

2.13.4

1 year ago

2.13.2

1 year ago

2.13.3

1 year ago

2.13.0

1 year ago

2.13.1

1 year ago

2.12.0

1 year ago

2.9.0

1 year ago

2.16.0

1 year ago

2.12.1

1 year ago

2.12.2

1 year ago

2.4.0

1 year ago

2.3.0

1 year ago

2.2.0

1 year ago

2.6.1

1 year ago

2.6.0

1 year ago

2.6.2

1 year ago

2.5.0

1 year ago

2.1.4

1 year ago

2.1.3

1 year ago

2.5.1

1 year ago

2.5.3

1 year ago

1.56.0

1 year ago

1.37.0

1 year ago

1.40.0

1 year ago

1.40.2

1 year ago

1.21.0

2 years ago

1.40.1

1 year ago

1.44.0

1 year ago

1.40.3

1 year ago

1.44.2

1 year ago

1.25.0

2 years ago

1.44.1

1 year ago

1.48.0

1 year ago

1.44.3

1 year ago

1.29.0

2 years ago

1.32.0

2 years ago

1.51.1

1 year ago

1.55.0

1 year ago

1.34.2

1 year ago

1.34.0

2 years ago

1.30.4

2 years ago

1.38.0

1 year ago

1.41.0

1 year ago

1.22.0

2 years ago

1.45.0

1 year ago

1.26.0

2 years ago

1.49.0

1 year ago

1.52.1

1 year ago

1.52.0

1 year ago

1.35.0

1 year ago

1.39.1

1 year ago

1.39.0

1 year ago

1.39.3

1 year ago

1.39.4

1 year ago

1.42.0

1 year ago

1.46.0

1 year ago

1.23.0

2 years ago

1.23.1

2 years ago

1.27.1

2 years ago

1.53.0

1 year ago

1.30.2

2 years ago

1.30.3

2 years ago

1.30.0

2 years ago

1.53.1

1 year ago

1.30.1

2 years ago

1.36.0

1 year ago

1.43.1

1 year ago

1.43.0

1 year ago

1.47.1

1 year ago

1.47.0

1 year ago

1.24.0

2 years ago

1.28.1

2 years ago

1.47.2

1 year ago

1.28.2

2 years ago

1.28.5

2 years ago

1.28.6

2 years ago

1.28.3

2 years ago

1.28.9

2 years ago

1.28.8

2 years ago

1.50.1

1 year ago

1.50.0

1 year ago

1.54.0

1 year ago

1.31.0

2 years ago

1.19.4

2 years ago

1.19.3

2 years ago

1.19.2

2 years ago

1.19.1

2 years ago

1.20.0

2 years ago

1.19.0

2 years ago

1.18.0

2 years ago

0.1.0

2 years ago