Когда я создавал библиотеку для валидации данных quartet, взял следующие цели-ориентиры:
- TypeScript
- Краткость и простота
- Производительность
В этой статье покажу как библиотека quartet понимает, что значат слова "Краткий" и "Простой" в контексте подхода к валидации.
Как работает quartet?
Когда он нужен? Он нужен тогда, когда нужна валидация данных
Что такое валидация? Проверка данных на соответствия требованиям. Чаще всего типам.
Как quartet в этом помогает? Он создает функцию валидации на основании декларативного описания требований — схемы. Функция-компилятор v преобразует схемы в функции валидации. Также внутри v хранятся заготовленные схемы и методы для создания более сложных схем. Детальнее смотрите в документации.
Как это работает?
- Пишешь схему
- Даешь ее на вход функции-компилятору
vчтобы получить функцию валидации. - Функция валидаци готова к использованию!
Давайте с помощью quartet отвалидируем все данные не напрягая мозг в семь подходов.
Начнём с легкого и постепенно будем увеличивать сложность требований.
Валидация типа с единственным примитивным значением
Например:
type Answer = 42;
Схема этого типа в quartet:
const answerSchema = 42;
Чтобы получить его функцию-валидатор пишем код:
import { v } from "quartet"; const checkAnswer = v(42);
Всё просто. Схема примитива — он сам.
Валидация встроенных типов
Предположим мы хотим валидировать не конкретную строку, а все строки. Или не одно конкретное число — а все числа.
Напишем пару валидаторов для примитивных типов. Посмотрим, что в них общее, а что нет.
const checkNumber = (x) => typeof x === "number"; const checkString = (x) => typeof x === "string"; const checkBoolean = (x) => typeof x === "boolean"; const checkSymbol = (x) => typeof x === "symbol"; // ...
У них одна структура:
const checkSomeType = x => typeof x === "<type>", где <type> – нужный нам тип
Итого для каждого примитивного типа, у нас есть схема:
| Тип | Схема |
|---|---|
'string' |
v.string |
'number' |
v.number |
'boolean' |
v.boolean |
'symbol' |
v.symbol |
Итак, мы можем получить функцию валидации числа таким образом:
const checkNumber = v(v.number); // то же, что const checkNumber = (x) => typeof x === "number";
Функции в quartet валидируются таким же способом
| Тип | Схема |
|---|---|
'function' |
v.function |
На практике это выглядит так:
const checkFunction = v(v.function); // то же, что const checkFunction = (x) => typeof x === "function";
Всё просто. Cхема типа — одноименный метод с префиксом v..
Операция «Или» для схем
Предположим у нас есть такой тип:
type NullableString = string | null;
Схема этого типа в quartet:
const nullableStringSchema = [v.string, null];
А функция валидации получим написав так:
const checkNullableString = v([v.string, null]);
В общем виде можно представить в виде типа:
type VariantSchema = Schema[];
Всё просто. Схема валидации вариантов – массив схем.
Операция «И» для схем
Допустим у нас есть объект у которого
type Password = string // Пароль должен быть длиннее 8-ми символов и содержать как минимум 1 букву и цифру.
Опишем эти требования с помощью схем. Для такого случая есть методы v.minLength() и v.test(RegExp)
const stringSchema = v.string; const min8Schema = v.minLength(8); const atLeastOneDigitSchema = v.test(/\d/); const atLeastOneLetterSchema = v.test(/[A-Za-z]/);
Чтобы объединить схемы у нас есть метод v.and():
const passwordSchema = v.and( stringSchema, min8Schema, atLeastOneDigitSchema, atLeastOneLetterSchema, )
Всё просто. Для нескольких проверок используй v.and(схема, схема, ...).
Валидация интерфейсов
Предположим есть интерфейс объекта Person:
interface Person { name: string age: number }
Что нужно знать, чтобы выполнить проверку обьекта на соответствие интерфейсу Person?
Необходимо знать, что у него есть поля с именами name и age. И они должны быть ти��а string и number. И больше ничего!
Таким образом нам нужно иметь возможность перечислить, какие у объекта должны быть поля, и какие схемы им соответствуют.
Наиболее подходящей для этого структурой данных является — объект!
Итак схема выглядит так:
const personSchema = { name: v.string, age: v.number, };
В общем виде схему интерфейса можно представить так:
interface ObjectInterfaceSchema { [propertyName: string]: Schema; }
Всё просто. Схема валидации интерфейса объекта — объект, в котором ключи — это поля объекта, а значения — схемы валидации.
Валидация масивов
Допустим у нас масив чисел:
type Numbers = number[];
Для этого у нас есть метод v.arrayOf(). На вход метода идёт схема елемента.
const checkNumbers = v(v.arrayOf(v.number));
Всё просто. Для валидации масива используй v.arrayOf(схема елемента).
Пользовательская валидация
А что если мы хотим встроить свою валидацию в схему quartet.
Давайте создадим схему проверки на парность числа. Для єтого напишем пользовательскую схему с помощью метода v.custom(своя функция валидации):
const evenSchema = v.custom((x) => x % 2 === 0) const evenNumberSchema = v.and( v.number, evenSchema, );
Всё просто. Для своих проверок используй v.custom(своя функция валидации).
Выводы
- Схема примитива — он сам.
- Cхема типа — одноименный метод с префиксом
v.. - Схема валидации вариантов — массив схем.
- Для нескольких проверок используй
v.and(схема, схема, ...). - Схема валидации интерфейса объекта — объект, в котором ключи — это поля объекта, а начения — схемы валидации.
- Для валидации масива используй
v.arrayOf(схема елемента). - Для своих проверок используй
v.custom(своя функция валидации).
P.S.
Я работаю в компании. Внедрение библиотеки quartet было так:
Поначалу разработчики подходили и спрашивали как написать проверку того или иного случая. После 4 вопросов они легко пишут валидации, которые им нужны.
Интересно послушать про ваш опыт работы с валидацией данных, напишите в комментариях об этом!
