ts-generate-validator

Task to generate validation functions to check whether JSON data conforms to a schema described as typescript classes

Usage no npm install needed!

<script type="module">
  import tsGenerateValidator from 'https://cdn.skypack.dev/ts-generate-validator';
</script>

README

Tool for code generation validators by typescript model description

Кодогенерация валидаторов на основе typescript моделей

Содержание:

Мотивация

В ходе анализа популярных библиотек для валидации, таких как:

  • validator.js
  • class-validator
  • yup
  • joi
  • ajv

стало понятно, что все они заставляют разработчика вручную писать валидационную схему. Т.е. во многом дублировать код на typescript.

Поэтому возникла идея сделать кодогенерацию валидации на основании TS описания. По примеру нашей библиотеки https://www.npmjs.com/package/grunt-generate-view-model

Что хотим от валидации:

  1. изоморфность (возможность использования как на клиенте, так и на сервере);
  2. не описывать отдельную схему валидации, а использовать typescript-описание;
  3. для случаев, выходящих за рамки TS (проверка на соотвествие regexp, минимального значения, максимального значения и т.п.) использовать декораторы;
  4. возможность использования кастомной валидации в виде функции, возможно асинхронной;
  5. мультиязычность (вывод ошибок валидации на разных языках);
  6. удобство использования;

Установка

npm install ts-generate-validator -D

Использование

  1. Создать внутри папки src класс-модель на typescript и добавить к этому классу декоратор @Validation:
// src/model.ts
import { Validation } from 'ts-generate-validator';

@Validation
export class User {
  public name: string = '';
  public age: number = 0;
  public website?: string;
}
  1. Добавить в раздел scripts файла package.json инициализирующую команду "generateValidator", например:
  "scripts": {
    "generation": "generateValidator"
  }
  1. Запустить кодогенерацию
npm run generation
  1. В папке src/generated/validators появится новый файл src.model.ts экспортирующий функцию-валидатор userValidator

  2. Использовать валидатор для проверки соответствия данных json схеме, описанной в typescript моделе:

// src/index.ts
import { ValidationException } from 'ts-generate-validator';
import { userValidator } from 'src/generated/validators';

const data = {
  name: 'John',
  age: 30
};

try {
  userValidator(data, { stopAtFirstError: true });
} catch (exception) {
  if (exception instanceof ValidationException) {
    console.log(`Неправильный формат данных`);
    exception.errors.map(({ field, message }) => `[${field}] - ${message}`).forEach((msg) => console.log(msg));
  } else {
    throw exception;
  }
}

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

Конфигурация кодогенерации

Для изменения конфигурации по умолчанию, создайте файл "ts-generate-validator-config.json" в корне проекта, содержащий объект с любыми из следующих свойств:

inputPath(string) - путь к папке с моделями для которых необходимо сгенерировать валидаторы (по умолчанию "src").

outputPath(string) - путь к папке, в которой появятся сгенерированные валидаторы (по умолчанию "src/generated/validators").

unknownPropertySeverityLevel(number) - уровень проверки неизвестных параметров (параметров, имеющих 1) тип в виде ссылки на другие классы моделей без @Validation декораторов или 2) не поддерживаемые типы без @CustomValidation декораторов). Может иметь следующие значения:

  • 2 (error) - при генерации валидаторов выбрасывается ошибка
  • 1 (warning) - кодогенерация проходит, но в консоли появляется предупреждение об отсутствии валидации
  • 0 (silent) - кодогенерация проходит

по умолчанию: 1 ("warning")

Например:

{
  "inputPath": "src/models",
  "outputPath": "generated/validators",
  "unknownPropertySeverityLevel": 2
}

Конфигурация валидаторов

Конфигурацию валидаторов можно изменить двумя способами

  1. Изменения глобальной конфигурации валидаторов с помощью функции changeConfig:
import { changeConfig } from 'ts-generate-validator';

changeConfig({ stopAtFirstError: true });
  1. Изменение конфигурации только для текущего вызова валидатора:
import { userValidator } from 'src/generated/validators';

userValidator(data, { stopAtFirstError: true });

Следующие параметры могут быть сконфигурированы:

stopAtFirstError(boolean) - не продолжать валидацию после первой найденной ошибки (по умолчанию false).

emailRegExp(RegExp) - заменяет регулярное выражение по умолчанию для проверки email.

messages(object) - словарь с сообщениями об ошибках (по умолчанию используются стандартные сообщения на английском).

Валидатор

Сгенерированная из класса-модели функция-валидатор данных является асинхронной, если хотя бы для одного из свойств в классе-модели добавлен асинхронный custom валидатор. Она принимает в качестве аргументов:

data(any) (обязательный) - JSON данные для проверки

config(object) (необязательный) - объект с параметрами конфигурации

context(any) (необязательный) - любое пользовательское значение, которое будет доступно в пользовательских валидаторах

Результат выполнения: Пустой результат в результате успешной валидации. Если валидация завершилась неудачей, то выбрасывается ValidationException.

Локализация

Сообщения об ошибке по умолчанию на английском языке, но они могут быть заменены на любые другие с помощью декораторов свойств классов-моделей (смотрите раздел декораторы для свойств классов), либо через установку параметра messages в конфигурации валидаторов (смотрите раздел конфигурация валидаторов).

Декораторы

Декораторы могут быть применены на классах, и на свойствах классов.

Для классов

  • @Validation(config?: ValidationConfig) - помечает класс, как требующий генерации функции-валидатора.

  • @RequiredOneOfValidation(fields: string[], message?: Message) - добавляет к нескольким свойствам класса, перечисленным в первом аргументе fields, валидатор, проверяющий заполненность как минимум одного из указанных полей.

Для свойств классов

  • all supported types (смотрите поддерживаемые типы)

    • @TypeValidation(message: Message) - заменяет стандартное сообщение об ошибке для валидатора типа.
  • all types

    • @CustomValidation(validator: CustomValidator, message: Message) - добавляет к свойству пользовательскую функцию-валидатор, которая принимает в качестве первого аргумента объект со следующими свойствами:

      property(any) - значение свойства для валидации

      propertyName(string) - имя свойства для валидации

      data(object) - объект с JSON данными для валидации

      config(object) - объект с текущей конфигурацией валидаторов

      context(any) - пользовательский контекст, переданный в сгенерированную функцию-валидатор

      Возвращаемое значение: в случае успешной валидации - ничего не возвращает, в случае когда данные не соответствуют типу - должна выбрасываться ошибка ValidationError, например:

      import { ValidationError } from 'ts-generate-validator';
      
      ({ property, propertyName }) => {
        const allowedValues = [1, 4, 9];
        if (!allowedValues.includes(property)) {
          throw new ValidationError(propertyName, 'is invalid');
        }
      };
      
    • @IgnoreValidation - помечает свойство как игнорируемое в функции-валидаторе - оно не будет проверяться на соответствие типу, а любые примененные декораторы для дополнительной валидацией будут проигнорированы.

  • boolean, number, string, null, enum, union(boolean, number, string, null), array(boolean, number, string, null)

    • @EqualValidation(value: EqualToValue, message?: Message) - добавляет валидатор, который проверяет соответствие значения свойства - значению, переданному в первом аргументе (в первом аргументе допускаются любые уровни вложенности в массивах).

    • @EqualToValidation(propName: string, message?: Message) - добавляет валидатор, который проверяет соответствие значения свойства, на котором был использован декоратор - свойству, содержащемуся в свойстве propName.

  • number, number[]

    • @MinValidation(threshold: number, message?: Message) - добавляет валидатор, который проверяет число на соответствие минимальному значению threshold(включительно).

    • @MaxValidation(threshold: number, message?: Message) - добавляет валидатор, который проверяет число на соответствие максимальному значению threshold(включительно).

    • @NegativeValidation(message?: Message) - добавляет валидатор, который проверяет - является ли число отрицательным.

    • @PositiveValidation(message?: Message) - добавляет валидатор, который проверяет - является ли число положительным.

    • @IntegerValidation(message?: Message) - добавляет валидатор, который проверяет - является ли число целым.

    • @FloatValidation(message?: Message) - добавляет валидатор, который проверяет - содержит ли число плавающую точку.

    • @LessThanValidation(propName: string, message?: Message) - добавляет валидатор, который проверяет - является ли число меньшим, чем число в свойстве propName.

    • @MoreThanValidation(propName: string, message?: Message) - добавляет валидатор, который проверяет - является ли число большим, чем число в свойстве propName.

  • string, string[]

    • @TrimValidation(message?: Message) - добавляет валидатор, который проверяет отсутствие пробелов в начале и конце строки.

    • @LowercaseValidation(message?: Message) - добавляет валидатор, который проверяет отсутствие заглавных букв в строке.

    • @UppercaseValidation(message?: Message) - добавляет валидатор, который проверяет отсутствие строчных букв в строке.

    • @MinLengthValidation(threshold: number, message?: Message) - добавляет валидатор, который проверяет длину строки на соответствие максимальному значению threshold(включительно).

    • @maxLength(threshold: number, message?: Message) - добавляет валидатор, который проверяет длину строки на соответствие максимальному значению threshold(включительно).

    • @EmailValidation(message?: Message) - добавляет валидатор, который проверяет - является ли строка корректным email адресом.

    • @UrlValidation(message?: Message) - добавляет валидатор, который проверяет - является ли строка корректным url адресом.

    • @MatchValidation(regexp: RegExp, message?: Message) - добавляет валидатор, который проверяет строку на соответствие регулярному выражению, переданному в первом аргументе.

Поддерживаемые типы

Тип Поддержка Определение типа по значению
boolean Да Да
number Да Да
string Да Да
null Да Да
undefined Нет Нет
enum Да Нет
array Частично (поддерживаются только массивы, элементы которых являются поддерживаемыми типами) Нет
union Частично (поддерживаются только объединения из свойств, имеющих поддерживаемый тип) Нет
class Частично (поддерживаются только классы, имеющие декоратор @Validation и находящиеся в каталогах, доступных для генерации валидаций - смотрите в конфигурации кодогенерации свойство inputPath) Нет
interface Нет Нет
type Нет (кастомные typescript типы не поддерживаются) Нет
typeof Нет Нет

Лицензия

MIT License