Skip to content

Валидация данных ​


Размер (checker): 0.8 KB

check проверяет объекты и массивы по схеме валидации. Поля, отсутствующие в схеме валидации, удаляются мутацией с опциональным логированием. Несоответствия возвращаются списком ошибок.

Благодаря компактному размеру библиотеки и схемам валидации итоговый бандл увеличивается незначительно, а нативные TS-типы не замедляют tsc и IDE, в отличие от библиотек, требующих описывать схемы валидации вручную и использующих сложное выведение типов.

ts
import { check } from '@arfa-tools/generator/api-contract-check/checker';

const schema = { request: { id: 'string' }, response: 'any' } as const;
const value = { id: 'user-1', debug: true };

const errors = check({
  schema,
  value,
  type: 'request',
  getExtraneous: (paths) => console.log(paths), // ['request.debug']
});

console.log(errors); // null
console.log(value); // { id: 'user-1' }

Примеры errors:

ts
[
  'request.id is not a string',
  'response.items[].name is missing',
  'response.role is none of "manager", "admin"',
];

Аргументы ​

ts
schema: TypeRootSchema

Полная схема валидации контракта { request, response }, возвращаемая createValidators

ts
type: 'request' | 'response'

Часть схемы валидации, по которой проверяется значение

ts
value: unknown

Проверяемые данные. Неизвестные поля объектов удаляются на месте (мутацией), даже при ошибках

ts
getExtraneous?: (
  paths: Array<string>
) => void

Получает уникальные пути удалённых полей. Вызывается один раз, только при наличии удалений, в том числе при ошибках проверки. Индексы массивов в путях заменяются на []

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

Обёртка для JSON POST-запросов использует сгенерированную схему валидации: проверяет запрос перед отправкой и ответ перед возвратом. Таким образом, TS-типы запроса и ответа полностью совпадают с реальными данными.

ts
import { check, type TypeRootSchema } from '@arfa-tools/generator/api-contract-check/checker';

async function request<TRequest, TResponse>({ url, validator, request }: {
  url: string;
  request: TRequest;
  validator: TypeRootSchema;
}): Promise<TResponse> {
  const requestErrors = check({ 
    schema: validator, 
    type: 'request', 
    value: request,
    getExtraneous: console.warn 
  });
  
  if (requestErrors) {
    throw new Error(`Request validation failed:\n${requestErrors.join('\n')}`);
  }

  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(request),
  });
  
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${url}`);

  const value: unknown = await response.json();
  
  const responseErrors = check({ 
    schema: validator, 
    type: 'response', 
    value, 
    getExtraneous: console.warn 
  });
  
  if (responseErrors) {
    throw new Error(`Response validation failed:\n${responseErrors.join('\n')}`);
  }

  return value as TResponse;
}

import type { TypeRequest, TypeResponse } from './api/getUser';
import { getUser } from './validators/api/getUser';

const user = await request<TypeRequest, TypeResponse>({
  url: '/api/getUser',
  request: { id: 'user-1' },
  validator: getUser,
});

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

Обёртка для WebSocket-запросов использует сгенерированную схему валидации: проверяет запрос перед отправкой и ответ перед возвратом. Таким образом, TS-типы запроса и ответа полностью совпадают с реальными данными.

Сервер в данном случае принимает JSON-сообщения { id, method, payload } и отвечает в формате { id, payload }.

ts
import { check, type TypeRootSchema } from '@arfa-tools/generator/api-contract-check/checker';

const socket = new WebSocket('wss://example.com/api');
const pending = new Map<string, (value: unknown) => void>();

socket.addEventListener('message', (event: MessageEvent<string>) => {
  const { id, payload } = JSON.parse(event.data);
  
  pending.get(id)?.(payload);
  
  pending.delete(id);
});

async function request<TRequest, TResponse>({ method, validator, payload }: {
  method: string;
  payload: TRequest;
  validator: TypeRootSchema;
}): Promise<TResponse> {
  const requestErrors = check({
    schema: validator,
    type: 'request',
    value: payload,
    getExtraneous: console.warn,
  });

  if (requestErrors) {
    throw new Error(`Request validation failed:\n${requestErrors.join('\n')}`);
  }

  const response = await new Promise<unknown>((resolve) => {
    const id = crypto.randomUUID();
    
    pending.set(id, resolve);
    
    socket.send(JSON.stringify({ id, method, payload }));
  });

  const responseErrors = check({
    schema: validator,
    type: 'response',
    value: response,
    getExtraneous: console.warn,
  });

  if (responseErrors) {
    throw new Error(`Response validation failed:\n${responseErrors.join('\n')}`);
  }

  return response as TResponse;
}

import type { TypeRequest, TypeResponse } from './api/getUser';
import { getUser } from './validators/api/getUser';

const user = await request<TypeRequest, TypeResponse>({
  method: 'getUser',
  payload: { id: 'user-1' },
  validator: getUser,
});

MIT License