OpenAPI спецификация описывает контракт HTTP API через формализованную схему: маршруты, параметры, тела запросов и ответы. Основная проблема при работе с такими контрактами заключается в необходимости синхронизации описания API и фактической валидации данных в рантайме. Именно здесь структурная валидация с использованием Superstruct становится практическим инструментом, позволяющим интерпретировать OpenAPI схемы как исполняемые проверки данных.
Superstruct оперирует композиционными структурами, где каждая сущность описывает правила проверки входных данных. OpenAPI 3.x, в свою очередь, описывает JSON Schema-подобные конструкции. Прямое соответствие между этими моделями не всегда очевидно, но может быть выстроено через трансляцию типов.
OpenAPI использует ограниченный набор примитивов:
Superstruct строит аналогичную систему через фабрики структур:
Простейшая трансляция выглядит линейной:
OpenAPI:
type: string
Superstruct:
import { string } fr om 'superstruct';
const Username = string();
Однако реальная сложность возникает при обработке ограничений.
OpenAPI допускает множество дополнительных модификаторов:
Superstruct реализует это через композицию и декораторы проверок.
Пример сопоставления:
OpenAPI:
type: string
minLength: 3
maxLength: 20
pattern: "^[a-zA-Z]+$"
Superstruct:
import { string, size, pattern } fr om 'superstruct';
const Username = pattern(
size(string(), 3, 20),
/^[a-zA-Z]+$/
);
Здесь важно, что Superstruct не хранит метаданные схемы, а выполняет только проверку, поэтому трансформация OpenAPI → Superstruct является однонаправленной.
Основной структурой в OpenAPI является object с набором свойств.
OpenAPI:
type: object
properties:
id:
type: integer
name:
type: string
required: [id, name]
Superstruct:
import { object, number, string } from 'superstruct';
const User = object({
id: number(),
name: string()
});
Обязательность полей в Superstruct задаётся фактом отсутствия optional-обёртки. Для OpenAPI optional поля необходимо явно оборачивать.
import { object, number, string, optional } from 'superstruct';
const User = object({
id: number(),
name: string(),
age: optional(number())
});
OpenAPI описывает массивы через items.
type: array
items:
type: string
Superstruct:
import { array, string } from 'superstruct';
const Tags = array(string());
Вложенные структуры транслируются рекурсивно:
type: object
properties:
tags:
type: array
items:
type: object
properties:
label:
type: string
const Tag = object({
label: string()
});
const Model = object({
tags: array(Tag)
});
OpenAPI поддерживает сложные композиции:
Superstruct реализует аналог через union и intersection.
oneOf:
- type: string
- type: number
import { union, string, number } from 'superstruct';
const Value = union([string(), number()]);
allOf:
- type: object
properties:
id:
type: number
- type: object
properties:
name:
type: string
import { intersection, object, number, string } from 'superstruct';
const A = object({ id: number() });
const B = object({ name: string() });
const Model = intersection([A, B]);
OpenAPI 3.x допускает nullable:
type: string
nullable: true
В Superstruct это выражается через union с null:
import { union, string, literal } from 'superstruct';
const NullableString = union([string(), literal(null)]);
OpenAPI:
type: string
enum: [pending, active, blocked]
Superstruct:
import { enums } from 'superstruct';
const Status = enums(['pending', 'active', 'blocked']);
Enum в Superstruct строго ограничивает множество допустимых значений, что полностью соответствует семантике OpenAPI.
OpenAPI часто использует format:
Superstruct не навязывает строгую семантику форматов, поэтому они реализуются через custom refinement.
import { string, refine } from 'superstruct';
const Email = refine(string(), 'Email', (value) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
);
Для UUID:
const UUID = refine(string(), 'UUID', (value) =>
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)
);
OpenAPI различает:
Каждый тип может быть преобразован в отдельную структуру.
in: query
name: lim it
schema:
type: integer
const Query = object({
lim it: optional(number())
});
requestBody:
content:
application/json:
schema:
type: object
properties:
title:
type: string
const Body = object({
title: string()
});
OpenAPI:
type: object
additionalProperties: false
Superstruct по умолчанию игнорирует дополнительные поля, поэтому строгая проверка требует дополнительного слоя:
import { object, Struct } from 'superstruct';
function strict(struct) {
return new Struct({
...struct,
validator: (value) => {
const keys = Object.keys(value);
const allowed = Object.keys(struct.schema);
return keys.every((k) => allowed.includes(k));
}
});
}
OpenAPI Components позволяют переиспользовать схемы:
components:
schemas:
User:
type: object
properties:
id:
type: integer
В Superstruct это выражается через композицию модулей:
const User = object({
id: number()
});
const Post = object({
author: User,
title: string()
});
Практический подход заключается в обходе AST OpenAPI документа и генерации Superstruct-схем.
Логика трансляции:
Упрощённый генератор:
function compile(schema) {
switch (schema.type) {
case 'string':
return string();
case 'number':
case 'integer':
return number();
case 'boolean':
return boolean();
case 'array':
return array(compile(schema.items));
case 'object':
const props = {};
for (const key in schema.properties) {
props[key] = compile(schema.properties[key]);
}
return object(props);
}
if (schema.enum) {
return enums(schema.enum);
}
if (schema.oneOf) {
return union(schema.oneOf.map(compile));
}
if (schema.allOf) {
return intersection(schema.allOf.map(compile));
}
}
OpenAPI часто используется не только для входных данных, но и для проверки ответов API.
const Response = object({
data: array(User),
meta: object({
total: number()
})
});
function validateResponse(data) {
return create(data, Response);
}
Это позволяет выявлять рассинхронизацию API-контракта и реализации сервера.
Superstruct легко интегрируется в HTTP-слои:
Пример middleware:
function validateBody(struct) {
return (req, res, next) => {
try {
req.body = create(req.body, struct);
next();
} catch (e) {
res.status(400).send(e.message);
}
};
}
Несмотря на структурное соответствие, существует ряд ограничений:
Это делает Superstruct не заменой OpenAPI, а runtime-слоем поверх него.
На практике применяется трёхуровневая модель:
Такая комбинация позволяет синхронизировать:
При этом Superstruct выступает связующим звеном между спецификацией и исполнением, обеспечивая строгую проверку данных без усложнения архитектуры приложения.