Payload validation
Size (checker): 0.8 KB
check validates objects and arrays against a validation schema. Fields absent from the schema are deleted in place by mutation, with optional logging. Mismatches are returned as a list of errors.
The compact library and validation schemas add little to the final bundle size. Native TS types also avoid slowing down tsc and the IDE, unlike libraries that require manually defined validation schemas and rely on complex type inference.
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' }Example errors:
[
'request.id is not a string',
'response.items[].name is missing',
'response.role is none of "manager", "admin"',
];Arguments
ts | The complete |
ts | The part of the validation schema used to check the value |
ts | The data to validate. Unknown object fields are deleted in place (by mutation), even when validation errors occur |
ts | Receives unique paths of deleted fields. Called once, only if fields were deleted, including when validation errors occur. Array indices in paths are replaced with |
Using with fetch
The wrapper for JSON POST requests uses a generated validation schema: it checks the request before sending it and the response before returning it. This ensures that the TS request and response types match the actual data exactly.
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,
});Using with WebSocket
The wrapper for WebSocket requests uses a generated validation schema: it checks the request before sending it and the response before returning it. This ensures that the TS request and response types match the actual data exactly.
In this example, the server accepts JSON messages in the { id, method, payload } format and responds with { 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,
});