Валидация данных
Размер (checker): 0.8 KB
check проверяет объекты и массивы по схеме валидации. Поля, отсутствующие в схеме валидации, удаляются мутацией с опциональным логированием. Несоответствия возвращаются списком ошибок.
Благодаря компактному размеру библиотеки и схемам валидации итоговый бандл увеличивается незначительно, а нативные TS-типы не замедляют tsc и IDE, в отличие от библиотек, требующих описывать схемы валидации вручную и использующих сложное выведение типов.
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:
[
'request.id is not a string',
'response.items[].name is missing',
'response.role is none of "manager", "admin"',
];Аргументы
ts | Полная схема валидации контракта |
ts | Часть схемы валидации, по которой проверяется значение |
ts | Проверяемые данные. Неизвестные поля объектов удаляются на месте (мутацией), даже при ошибках |
ts | Получает уникальные пути удалённых полей. Вызывается один раз, только при наличии удалений, в том числе при ошибках проверки. Индексы массивов в путях заменяются на |
Использование в fetch
Обёртка для JSON POST-запросов использует сгенерированную схему валидации: проверяет запрос перед отправкой и ответ перед возвратом. Таким образом, 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 }.
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,
});