Когда я создавал библиотеку для валидации данных quartet, взял следующие цели-ориентиры:



В этой статье покажу как библиотека quartet понимает, что значат слова "Краткий" и "Простой" в контексте подхода к валидации.


Как работает quartet?


Когда он нужен? Он нужен тогда, когда нужна валидация данных
Что такое валидация? Проверка данных на соответствия требованиям. Чаще всего типам.
Как quartet в этом помогает? Он создает функцию валидации на основании декларативного описания требований — схемы. Функция-компилятор v преобразует схемы в функции валидации. Также внутри v хранятся заготовленные схемы и методы для создания более сложных схем. Детальнее смотрите в документации.


Как это работает?


  1. Пишешь схему
  2. Даешь ее на вход функции-компилятору v чтобы получить функцию валидации.
  3. Функция валидаци готова к использованию!

Давайте с помощью 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 вопросов они легко пишут валидации, которые им нужны.


Интересно послушать про ваш опыт работы с валидацией данных, напишите в комментариях об этом!