Не верим мы разработчикам и точка.

Или о том, как мы подошли к вопросу о валидировании схем 

Обо мне

  • SDET from 2018
  • Senior SDET @b2broker
  • certified node.js application developer
  • TG - @haradkou_sdet

План

  • Контекст проекта
  • Паиплаин разработки
  • С какой проблемой столкнулись
  • Как решали
  • К чему пришли

Контекст проекта

  • Трейдинг терминал
  • Документация
  • Много интеграций
  • Богатый API (Swagger)
  • Есть WS (не swagger)

Трейдинг терминал

Фото из официального сайта b2broker.com

Фото из официального сайта b2broker.com/bbp

Документация

  • Confluence
  • IFR
  • DFR

DFR

  • Диаграммы
  • Описание структур БД, полей
  • Описание запросов и ответов

Проблема

  • DFR может поменяться без изменения полей
  • DFR может поменяться с изменением полей ответа
  • Задача проводится как баг
  • проверка занимает много времени для стендов (10+)

Решение

JSON schema

  • много текста
  • большая когнитивная нагрузка
  • сложно\долго решаются конфликты

JSON Schema

JSON Schema

Ресерч!

Какие есть решения?

  • ajv
  • djv
  • json-schema-library
  • zod
  • joi
  • typebox

Чем выделяется zod?

import z from 'zod'

const Order = z.object({
  orderId: z.string(),
  items: z.array(z.object({
    name: z.string(),
    price: z.number(),
  }))
})

type OrderApi = z.infer<typeof Order> 
// {orderId: string, items: {name: string, price: number}[] }

Какие минусы

  • Не являются имплементацией JSON-schema
  • имеют поддержкку типов не характерные JSON-schema (function, bigint, symbol, ....)
  • для генерации JSON-Schema нужно больше библиотек

AJV!

const Ajv = require("ajv")
const ajv = new Ajv()

const schema = {
  properties: {
    foo: {type: "int32"}
  },
  optionalProperties: {
    bar: {type: "string"}
  }
}


const data = {foo: 1, bar: "abc"}
const valid = ajv.validate(schema, data)

Плюсы ajv

  • большое комьюнити
  • широкое распростанение в продакшенах
  • умеет в draft и swagger
  • поддержка типов только характерных JSON-Schema

Минусы ajv

  • длинное описание схем
  • нужно читать не сверху вниз

Какой итог

  • ajv
  • нравится синтаксис zod
  • генерировать json-schema - появилось в процессе

Что делаем?

export class SchemaBuilder {
  _schema: any
  /**
   * returns JSON-schema representation
   */
  get schema() {
    return this._schema;
  }
  /**
   * Marks your property as nullable (`null`).
   *
   * Updates `type` property for your schema.
   * @example
   * const schemaDef = s.string().nullable()
   * schemaDef.schema // { type: ['string', 'null'], nullable: true }
   */
  nullable(): SchemaBuilder {
    // implementation
   return this
  }
}

Как используем?

// responseModels.ts
import s from 'ajv-ts'

const Order = s.object({
  orderId: s.string(),
  items: s.array(s.object({
    name: s.string(),
    price: s.number(),
  }))
})

Order.schema 
// {type: 'object', properties: { orderId: {type: 'string'} } }

export default {
  'mySchema.Enteties.Order': Order
}

Генерируем!

Плюсы

  • Декларативный и удобный API
  • Есть JSDoc с примерами
  • полная поддержка JSON-Schema
  • можно подкинуть свой AJV инстанс
  • поддержка pre/post parsing hooks
  • Typescript runtime validation
  • полная поддержка cjs/mjs

Минусы

  • Малое распространение
  • хайп на имени AJV
  • работа с кастомными полями сделана не очень
  • можно подменить свойство schema на свою в runtime
  • нет three shaking (в планах)
  • не поставляем .min.js
  • скорость парсинга падает если использовать pre/post hooks
  • прилось пойти на ухищрения TS (привет undefined)

Какие еще есть применения

  • Парсинг .env переменных с использованием pre/post hooks
  • валидация опций для cli утилит

А как попробовать у себя?

Bonus: Валидация min/max

import s from 'ajv-ts'

const Order = s.object({
  orderId: s.string(),
  name: s.string().min(5).max(2) // ! Typescript Error !
})

References

  • Telegram - Haradkou.SDET
  • Github - vitalics
  • Github - vitalics/ajv-ts

Heisenbug 2024 Autumn

By vitalic gorodkov

Heisenbug 2024 Autumn

  • 144