Проблема
Всего пару недель назад я попробовал поток для новой серверной службы и обнаружил, что это довольно крутой инструмент (если его можно так назвать) для обеспечения безопасности кода. Несколько слов о сервисе: он доступен через REST и MQ для других сервисов, этот сервис генерирует некоторые текстовые сообщения типа переданного в зависимости от переданных параметров. Например:

// Request body
{
  type: ‘SpectatorsMessage’,
  parameters: { eventId: 12345 }
}

Предположим, что у вас есть много классов сообщений с разными типами, требуемыми в конструкторе.

// @flow
// messages.js
type Params = { eventId: number };
export class SpectatorsMessage {
  constructor(params: Params) {…}
}
// many other messages

И когда я реализовывал логику в генерации сообщений, отображении данных и т. д., я был очень доволен потоком, но когда я добавил простой маршрут для генерации этих сообщений, я застрял.

import * as messages from ‘my-messages’;
router.post(‘/message’, ctx => {
  const { type, parameters } = ctx.request.body;
  const Message = messages[type];
  if (Message) {
  // and here I would like to do some validation
  // of parameters for this type of message

Я надеюсь, вы понимаете, что поток является статическим средством проверки типов и не обеспечивает никаких проверок во время выполнения. Для этого есть модуль flow-runtime, но он медленный для продакшена, и я не хотел проверять весь код.

Хотя я не хотел копировать потоковые типы и преобразовывать их в какие-то функции валидатора, json-схему или другое. Я пытался найти что-то приемлемое для этой цели (модуль npm, конечно), но ничего.

Решение:

Я решил сделать свой собственный бабелевский плагин (наверное, лучше сделать скрипт codemod), который преобразует потоковые типы в JSON-схему. Теперь этот класс сообщений будет выглядеть так:

// IN:
type Params = { eventId: number };
export class SpectatorsMessage {
  // $flow-to-json
  static propTypes: Params;
  constructor(params: Params) {…}
}
// OUT:
export class SpectatorsMessage {
  // $flow-to-json
  static propTypes = {
    type: 'object',
    properties: { eventId: { type: 'number' } },
    required: ['eventId']
  };
  constructor(params: Params) {…}
}

Комментарий $flow-to-json используется, чтобы показать подключаемому модулю Babel, что вы хотите преобразовать это свойство в json-схему переданного типа (он обрабатывает любые статические свойства в классах).

Итак, теперь у меня есть действующая схема JSON (черновик 6), и я могу легко проверять свойства, которые я хочу проверить во время выполнения. Он охватывает множество вариантов использования (все мои, например), теперь осталось не так много нереализованных вещей. Вы можете проверить репозиторий, посмотреть, что реализовано, а что нет в ридми. Я буду рад отзывам, если вы протестируете его, создадите проблему, PR или просто поставите ему звезду.

P.S. Извините, если пример в этой статье не был ясен или странен, но в ридми репозитория вы можете найти, вероятно, более очевидный пример.