Спецификация OpenAPI
OpenAPI — это спецификация для документирования API в формате YAML или JSON. Она позволяет стандартизированно описывать REST API.
Swagger UI и ReDoc — это популярные инструменты, которые преобразуют спецификацию OpenAPI в интерактивную документацию (UI для спецификации OpenAPI).
Как найти исходную спецификацию OpenAPI по Swagger UI или ReDoc? Открыв документацию Swagger UI или ReDoc в браузере, в консоли разработчика можно найти сетевые запросы к файлу спецификации (формата *.json, *.yaml или *.yml и содержимое будет начинаться с версии спецификации OpenAPI).
Как найти исходную спецификацию OpenAPI по Swagger UI или ReDoc?
Как можно применить спецификацию OpenAPI?
1. Генерация типов TypeScript
Одно из главных преимуществ — возможность генерировать TypeScript типы (для всех эндпоинтов, request/response моделей) из спецификации. Особенно полезно в небольших стартапах или на начальных этапах разработки проекта, когда api быстро меняется, а изменения не всегда обратно совместимы, и вручную уследить за всеми изменениями сложно.
Примеры инструментов:
-
openapi-typescript — генерирует чистые TypeScript-типы (paths, components) из OpenAPI 3.0/3.1 без runtime-кода, быстро и с поддержкой YAML/JSON (локально/удалённо);
-
dtsgenerator — Создаёт .d.ts файлы из JSON Schema или OpenAPI (v2/v3), с CLI, плагинами и поддержкой stdin/url;
-
openapi-generator — Универсальный генератор (CLI @openapitools/openapi-generator-cli) для клиентов, серверов, документации в 50+ языках, включая TypeScript-варианты (axios, fetch);
1.1. Пример генерации типов для RTK
Полный код рабочего примера на github
Как это работает:
1.1.1. Генерируем типы из OpenAPI спецификации через npm команду
// ..."scripts": { //... "generate-types": "dtsgen --out src/api/types/generated-counter-api-types.d.ts specs/prepared-counter-api.yaml", //...}// ...
1.1.2. Генерируются файлы с API типами
// ...declare namespace Components { namespace Schemas { export interface CounterResponse { value: number; // int32 } export interface CounterRequest { value: number; // int32 } }}declare namespace Paths { namespace GetCounter { export interface QueryParameters { version?: string; } namespace Responses { export type $200 = Components.Schemas.CounterResponse; } } namespace SaveCounter { export type RequestBody = Components.Schemas.CounterRequest; namespace Responses { export type $200 = Components.Schemas.CounterResponse; } }}// ...
1.1.3. Создаём RTK Query API с использованием сгенерированных типов файл
/// // ...export const counterApi = createApi({ // ... endpoints: (builder) => ({ getCounter: builder.query({ // ... }), saveCounter: builder.mutation({ // ... }), }),});// ...
1.1.4. Используем типобезопасные хуки в React компонентах файл
// ...const { data, isLoading, error } = useGetCounterQuery({ version });const [saveCounter] = useSaveCounterMutation();// data имеет тип Paths.GetCounter.Responses.$200// saveCounter принимает параметры типа Paths.SaveCounter.RequestBody// ...
1.2. Пример генерации типов для react-query
Полный код рабочего примера на github
Как это работает детально:
1.2.1. Генерируем типы из OpenAPI спецификации через npm команду
// ..."scripts": { // ... "generate-types": "dtsgen --out src/api/types/generated-api-types.d.ts specs/prepared-openapi.json" // ...}// ...
1.2.2. Генерируются файлы с API типами
declare namespace Components { namespace Schemas { export interface Pet { id?: number; // int64 name: string; photoUrls: string[]; status?: "available" | "pending" | "sold"; category?: Category; tags?: Tag[]; } }}declare namespace Paths { namespace GetPetById { export interface PathParameters { petId: number; // int64 } namespace Responses { export type $200 = Components.Schemas.Pet; } }}
1.2.3. Создаём API-функции с использованием сгенерированных типов
/// export async function fetchPetById(petId: number): Promise { const res = await fetch(`http://localhost:4010/api/v3/pet/${petId}`); if (!res.ok) { throw new Error('Pet is not found'); } return res.json();}
1.2.4. Используем типобезопасные хуки React Query в компонентах
import { useQuery } from '@tanstack/react-query';import { fetchPetById } from './api/fetchPetById';// ...function App() { // ... const { isPending, error, data } = useQuery({ queryKey: ['pet', petId], queryFn: () => fetchPetById(petId), }); // ...}
1.3. Пример генерации типов для нативного fetch
Полный код рабочего примера на github
Как это работает детально:
1.3.1. Генерируем типы из OpenAPI спецификации через npm команду
// ..."scripts": { //... "generate-types": "dtsgen --out src/api/types/generated-api-types.d.ts specs/prepared-openapi.json", //...}// ...
1.3.2. Генерируются файлы с API типами
// ...declare namespace Components { namespace Schemas { export interface Pet { id?: number; // int64 name: string; photoUrls: string[]; status?: "available" | "pending" | "sold"; category?: Category; tags?: Tag[]; } }}declare namespace Paths { namespace GetPetById { export interface PathParameters { petId: number; // int64 } namespace Responses { export type $200 = Components.Schemas.Pet; export interface $404 {} } }}// ...
1.3.3. Создаём типобезопасную обёртку над нативным fetch с использованием сгенерированных типов файл
/// export class ApiClient { private baseUrl: string; constructor(baseUrl: string) { this.baseUrl = baseUrl; } async get(path: string, options?: RequestInit): Promise<ApiResult> { const url = `${this.baseUrl}${path}`; const res = await fetch(url, { ...options, method: 'GET' }); if (!res.ok) { const error = new ApiError(res.statusText, res.status); return [error, null]; } const data = (await res.json()) as T; return [null, data]; } // Специфичный метод с использованием сгенерированных типов async fetchPetByIdSafe(petId: number): Promise<ApiResult> { return this.get(`/api/v3/pet/${petId}`); }}
1.3.4. Используем типобезопасный API клиент в React компонентах файл
import { ApiPet, ApiTag } from './api/types/models';import { ApiClient, ApiError } from './api/client';// ...const apiClient = new ApiClient('http://localhost:4010');// ...function App() { // ... const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); // ... useEffect(() => { const fetchPet = async () => { setIsLoading(true); setError(null); const [err, pet] = await apiClient.fetchPetByIdSafe(petId); if (err) { setError(err.message); } else { setData(pet); } setIsLoading(false); }; fetchPet(); }, [petId]); // ...}
2. Генерация API-клиентов
Из OpenAPI спецификации можно генерировать готовые API-клиенты.
Не нужно вручную писать fetch/axios запросы, автоматическая обработка headers, query параметров.
Примеры инструментов:
-
orval https://www.npmjs.com/package/orval — Генерирует React Query/TanStack Query, Axios/Fetch клиенты с типами из OpenAPI v3/Swagger v2.
-
Hey API (@hey-api/openapi-ts) https://www.npmjs.com/package/@hey-api/openapi-ts — CLI для клиентов (Fetch, Axios, Angular, Next/Nuxt), SDK, валидаторов; OpenAPI 2/3/3.1 с плагинами.
-
openapi-typescript-codegen https://www.npmjs.com/package/openapi-typescript-codegen — Генерирует Axios/Fetch клиенты с типами, поддержка JSON/YAML, external refs
-
swagger-typescript-api https://www.npmjs.com/package/swagger-typescript-api — CLI для Fetch/Axios API клиентов из Swagger/OpenAPI
-
@openapitools/openapi-generator-cli https://www.npmjs.com/package/@openapitools/openapi-generator-cli — Универсальный CLI для SDK в 50+ языках/фреймворках (TS-axios, fetch и др.).
-
@rtk-query/codegen-openapi https://www.npmjs.com/package/@rtk-query/codegen-openapi — Генерирует RTK Query (Redux Toolkit) хуки/endpoint’ы из OpenAPI.
-
openapi-fetch https://www.npmjs.com/package/openapi-fetch — Легкий (6kb) type-safe Fetch-клиент на runtime (с типами из openapi-typescript)
2.1. Пример сгенерированного клиента с использованием orval
Полный код рабочего примера на github
Как это работает детально:
2.1.1. Пишем конфигурацию
export default defineConfig({ api: { input: { target: './specs/prepared-openapi.json', }, output: { target: './src/api/generated/api.ts', client: 'fetch', mode: 'tags', baseUrl: 'http://localhost:4010', }, },});
2.1.2. Запускаем генерацию через npm комманду
// ..."scripts": { //... "generate-types": "orval", //...}// ...
2.1.3. Генерируются файлы с api клиентами
// ...export const getPetById = async (petId: number, options?: RequestInit): Promise => { // ... const res = await fetch(getGetPetByIdUrl(petId)); // ...}// ...
2.1.4. Генерируются файлы с api типами
// ...export interface Pet { category?: Category; id?: number; name: string; photoUrls: string[]; /** pet status in the store */ status?: PetStatus; tags?: Tag[];}// ...
3. Типизация endpoints в BFF-прокси на Node.js с Express
Автоматическая типизация запросов/ответов (генерируя общие TypeScript-типы из API-контрактов из OpenAPI спецификации), исключает ошибки парсинга данных на фронте и упрощает интеграцию с React/TypeScript без ручного описания интерфейсов. Фронтенд получает “глупый” клиент с полной автодополняемостью и валидацией на этапе компиляции, снижая время на отладку API-интеграций. Это особенно полезно в микросервисах, где BFF адаптирует backend-API под нужды UI.
3.1. Пример типизации express endpoints через общие TypeScript-типы
Полный код рабочего примера на github
Как это работает:
3.1.1. Генерируем типы из OpenAPI спецификации через npm команду
"scripts": { //... "generate-types": "dtsgen --out src/services/petstore/generated-api-types.d.ts specs/prepared-petstore.json", //...}
3.1.2. Генерируются файлы с API типами
// ...declare namespace PetstoreComponents { namespace Schemas { // ... export interface Order { complete?: boolean; /** * example: * 10 */ id?: number; // int64 /** * example: * 198772 */ petId?: number; // int64 /** * example: * 7 */ quantity?: number; // int32 shipDate?: string; // date-time /** * Order Status * example: * approved */ status?: "placed" | "approved" | "delivered"; } // ... }}declare namespace PetstorePaths {// ... namespace StoreOrder$OrderId { namespace Get { namespace Parameters { export type OrderId = number; // int64 } export interface PathParameters { orderId: Parameters.OrderId /* int64 */; } namespace Responses { export type $200 = PetstoreComponents.Schemas.Order; export interface $400 { id?: number; // int64 } export interface $404 { message?: string; } } } } // ...}
3.1.3. Определяем типобезопасный контроллер через интерфейс TypedController
// ...export type TypedController = ( request: TypedRequest, response: TypedResponse, next: TypedNext,) => Promise | void;// ...
3.1.4. Создаём типобезопасные контроллеры с использованием сгенерированных типов
// Определяем тип маршрута с параметрами запроса и возможными ответамиtype GetOrderByIdRoute = { request: { params: { orderId: PetstorePaths.StoreOrder$OrderId.Get.Parameters.OrderId; }; }; responses: { success: PetstorePaths.StoreOrder$OrderId.Get.Responses.$200; error: | PetstorePaths.StoreOrder$OrderId.Get.Responses.$400 | PetstorePaths.StoreOrder$OrderId.Get.Responses.$404; };}// ...// Типизированный контроллер для маршрута GetOrderByIdconst getOrderByIdRouteTypedController: TypedController = async (request, response, next) => {// ...const responseMock = await getMockFromOpenApi('post', '/store/order', '200'); // ... response.result = responseMock; next(); // ... // Example: not valid response body // Intentionally assign an invalid object to demonstrate TypeScript error checking // @ts-expect-error: TS2322: Type '{ foo: string; }' is not assignable to type 'Order'. Object literal may only specify known properties, and 'foo' does not exist in type 'Order'. response.result = { foo: 'bar'};}// Регистрируем маршрут с типобезопасным контроллеромapp.get('/store/order/:orderId', getOrderByIdRouteTypedController);
3.1.5. Примеры типобезопасных контроллеров для разных типов запросов:
POST запрос с телом:
type PlaceOrderRoute = { request: { body: PetstorePaths.StoreOrder.Post.RequestBody; }; responses: { success: PetstorePaths.StoreOrder.Post.Responses.$200; error: | PetstorePaths.StoreOrder.Post.Responses.$400 | PetstorePaths.StoreOrder.Post.Responses.$422; };}const placeOrderRouteTypedController: TypedController = async (request, response, next) => { const order = request.body; // order типизирован как PetstoreComponents.Schemas.Order // ...}
GET запрос с query параметрами:
type LoginUserRoute = { request: { query: PetstorePaths.UserLogin.Get.QueryParameters; }; responses: { success: PetstorePaths.UserLogin.Get.Responses.$200; error: PetstorePaths.UserLogin.Get.Responses.$400; };}const loginUserRouteTypedController: TypedController = async (request, response, next) => { const username = request.query.username; // типизирован как string | undefined const password = request.query.password; // типизирован как string | undefined // ...}
Примеры других инструментов:
-
tRPC https://trpc.io/ — Полная end-to-end типизация без схем; интегрируется с Express, идеален для React+BFF; типы генерируются автоматически из роутеров
-
routing-controllers https://github.com/typestack/routing-controllers — Декораторы для классов-контроллеров в Express; типизирует handlers с @Get/@Post, поддерживает валидацию
4. Валидация в обработчиках (handlers) Express
OpenAPI спецификация значительно упрощает валидацию в обработчиках (handlers) Express, обеспечивая автоматическую проверку запросов и ответов на соответствие заранее определённым схемам.
Примеры библиотек:
-
express-openapi-validator https://www.npmjs.com/package/express-openapi-validator — предоставляет Express middleware для автоматической валидации запросов, ответов (JSON) и security по OpenAPI 3.0/3.1
-
openapi-request-validator https://www.npmjs.com/package/openapi-request-validator — валидирует свойства запросов (query, body, headers, params) против OpenAPI схем, работает standalone
-
openapi-response-validator https://www.npmjs.com/package/openapi-response-validator — проверяет HTTP-ответы по responses схемам OpenAPI, standalone для тестирования или runtime
4.1. Пример валидации запросов
Полный код рабочего примера на github
Как это работает:
Нужно добавить middleware для валидации запросов и в аргументы пробросить OpenApi спецификацию:
app.use( OpenApiValidator.middleware({ apiSpec: openApiSpec, validateRequests: true, validateResponses: true, }),);
5. Генерация моковых/sample данных/сущностей для разработки и интеграционных тестов
OpenApi спецификация может использоваться для автоматической генерации сущностей и моделей данных, которые можно использовать в интеграционных тестах, обеспечивая их согласованность с контрактом API и ускоряя QA-процессы.
Примеры инструментов:
-
openapi-sampler https://www.npmjs.com/package/openapi-sampler — генерирует детерминированные примеры JSON из OpenAPI Schema Objects (3.0/3.1), учитывая examples, enum, default, constraints и compound keywords как allOf/oneOf/anyOf
-
@openapi-contrib/openapi-schema-to-json-schema https://www.npmjs.com/package/@openapi-contrib/openapi-schema-to-json-schema + json-schema-faker https://github.com/json-schema-faker/json-schema-faker — @openapi-contrib/openapi-schema-to-json-schema конвертирует OpenAPI Schema/Parameter в JSON Schema Draft 4 (удаляет nullable → type: [‘null’], поддержка nested allOf), для совместимости с JSON Schema инструментами. json-schema-faker (jsf) генерирует случайные (не детерминированные) данные из JSON Schema Draft 4+.
5.1. Пример генерации моковых данных
Полный код рабочего примера на github
Генерация моковых/sample данных основана на конвертации OpenAPI схем в JSON Schema и использовании json-schema-faker https://www.npmjs.com/package/json-schema-faker:
-
@openapi-contrib/openapi-schema-to-json-schema – конвертирует OpenAPI схемы в JSON Schema
-
@apidevtools/json-schema-ref-parser – разрешает все
$refссылки в OpenAPI спецификации -
json-schema-faker – генерирует валидные данные на основе JSON Schema
Как это работает детально:
5.1.1. Генерация sample данных для сущностей (компонентов) из OpenAPI:
// Утилита для генерации sample объекта для сущности из OpenAPI спецификации// ...async function createEntitySampleFromOpenApi(entityName: string): Promise { // Загружаем OpenAPI спецификацию const specPath = path.join(__dirname, '../specs/petstore.json'); const spec = JSON.parse(fs.readFileSync(specPath, 'utf-8')); // Разрешаем все $ref ссылки const dereferenced: any = await RefParser.dereference(spec); // Находим схему для указанной сущности/компонента const entitySchema = dereferenced.components.schemas[entityName]; if (!entitySchema) throw new Error('Component not found'); // Конвертируем OpenAPI схему в JSON Schema const jsonSchema = openapiSchemaToJsonSchema(entitySchema); // Генерируем sample объект, соответствующий схеме return jsf.generate(jsonSchema) as T;}// Использование в тестахit('should generate a valid Pet object', async () => { // Генерируем sample объект для сущности 'Pet', определённой в OpenAPI спецификации const sample = await createEntitySampleFromOpenApi('Pet'); // sample типизирован как PetstoreComponents.Schemas.Pet expect(sample).toBeTruthy();});
5.1.2. Генерация sample данных для ответов endpoint’ов:
// Утилита для генерации sample ответа для endpoint'а из OpenAPI спецификацииasync function createEndpointResponseSampleFromOpenApi(method: string, endpointPath: string, statusCode: string): Promise { // Загружаем OpenAPI спецификацию const specPath = path.join(__dirname, '../specs/petstore.json'); const spec = JSON.parse(fs.readFileSync(specPath, 'utf-8')); // Разрешаем все $ref ссылки const dereferenced: any = await RefParser.dereference(spec); // Находим указанный endpoint const pathItem = dereferenced.paths[endpointPath]; if (!pathItem) throw new Error('Path not found'); // Находим указанный HTTP метод const op = pathItem[method.toLowerCase()]; if (!op) throw new Error('Method not found'); // Извлекаем ответ для указанного статус кода const resp = op.responses && op.responses[statusCode]; if (!resp) throw new Error('Status code not found'); // Получаем JSON схему для ответа const content = resp.content && resp.content['application/json']; if (!content || !content.schema) throw new Error('Response schema not found'); // Конвертируем OpenAPI схему в JSON Schema const jsonSchema = openapiSchemaToJsonSchema(content.schema); // Генерируем sample объект, соответствующий схеме return jsf.generate(jsonSchema) as T;}// Использование в тестахit('should generate a valid endpoint response', async () => { // Генерируем sample ответ для GET /pet/{petId} endpoint со статусом 200 const sampleResponse = await createEndpointResponseSampleFromOpenApi( 'get', '/pet/{petId}', '200' ); // sampleResponse типизирован как PetstorePaths.Pet$PetId.Get.Responses.$200 expect(sampleResponse).toBeTruthy();});
5.1.3. Генерация моковых данных для использования в контроллерах:
// Утилита для генерации моковых данных из OpenAPI (используется в контроллерах)async function getMockFromOpenApi(method: string, endpointPath: string, statusCode: string): Promise { const specPath = path.join(__dirname, '../specs/petstore.json'); const spec = JSON.parse(fs.readFileSync(specPath, 'utf-8')); const dereferenced: any = await RefParser.dereference(spec); const pathItem = dereferenced.paths[endpointPath]; const op = pathItem[method.toLowerCase()]; const resp = op.responses && op.responses[statusCode]; const content = resp.content && resp.content['application/json']; const jsonSchema = openapiSchemaToJsonSchema(content.schema); return jsf.generate(jsonSchema) as T;}// Использование в контроллереconst getOrderByIdRouteTypedController: TypedController = async (request, response, next) => { // ... // Генерируем моковый ответ для успешного ответа 200 const responseMock = await getMockFromOpenApi( 'get', '/store/order/{orderId}', '200' ); response.result = responseMock; // типобезопасно next();// ... // Example 4: valid error body // Generate a mock error response for a 400 error using OpenAPI types const responseErrorMock = await getMockFromOpenApi('post', '/store/order/{orderId}', '400'); next( new ServiceResponseError({ statusCode: 400, body: responseErrorMock }), ); // ...};
6. Создание mock-серверов
OpenAPI спецификация значительно упрощает создание mock-серверов, позволяя автоматически генерировать имитацию API на основе описанных эндпоинтов, схем данных и примеров ответов. Инструменты вроде MockServer, Smogger или Swagger Mock загружают спецификацию (в формате YAML/JSON) и динамически создают сервер, который возвращает реалистичные мок-ответы с рандомными данными по схемам, без необходимости писать код вручную. Это ускоряет frontend-разработку, тестирование и интеграцию, пока реальный бэкенд не готов, обеспечивая валидацию запросов и поддержку всех HTTP-методов из спецификации.
Примеры инструментов:
-
Prism (@stoplight/prism-cli) https://github.com/stoplightio/prism запускает полноценный HTTP mock/proxy-сервер из OpenAPI 2/3 или Postman, с динамическими ответами, валидацией и transformations
-
msw + msw-auto-mock https://www.npmjs.com/package/msw-auto-mock — перехватывает запросы в браузере/Node (Service Worker), генерируя TS-моки из OpenAPI с faker.js данными для dev-тестов
-
json-server https://www.npmjs.com/package/json-server — создаёт REST API из JSON-файла (db.json), с CRUD, auth-плагинами (json-server-auth), но без native OpenAPI — нужны кастомные плагины для схем
6.1. Пример запуска mock-сервера
Полный код рабочего примера на github
Как это работает детально:
Запускаем mock-сервер с помощью Prism CLI https://github.com/stoplightio/prism:
"scripts": { //... "mock:api": "prism mock specs/prepared-openapi.json -h 0.0.0.0 -p 4010 --cors", //...}// npm run mock:api
Mock-сервер:
-
Читает OpenAPI спецификацию из
specs/prepared-openapi.json -
Автоматически генерирует валидные ответы на основе схем из спецификации
-
Запускается на
http://localhost:4010 -
Поддерживает CORS для работы с фронтенд-приложением
-
Эмулирует все endpoint’ы, определённые в OpenAPI спецификации
Возникающие типичные проблемы и как их решить
Фронтендеры часто сталкиваются с тем, что OpenAPI документация от backend-команды неидеальна и мешает эффективной работе:
-
допускаются банальные опечатки, если бэкендеры пишут спецификацию вручную;
-
стандарт OpenAPI формальный, из-за этого иногда спецификацию пишут так, что ломаются интеграции с инструментами (например, из-за особенностей написания спецификации перестаёт работать генерация типов);
-
если бэкендеры генерируют спецификацию из кода, она может формироваться так, что ломаются интеграции с инструментами работы со спецификацией — возникает дилемма: либо дорабатывать фронтенд-инструменты, либо исправлять генерацию спецификации на бэкенде.
В результате часть автоматизации теряет смысл, требуя ручных правок и уточнений.
Есть несколько способов решить эту проблему:
1. Валидировать OpenApi спецификацию
Инструменты для валидации OpenApi спецификации:
-
Redocly CLI выполняет linting с рекомендациями по улучшению, поддерживает OpenAPI 3.x/2.0, bundling, документацию и API contract testing через Respect.
-
Spectral — это линтер на основе правил (rulesets/functions), ориентированный на OpenAPI, AsyncAPI и другие JSON/YAML, с высокой кастомизацией.
-
Swagger CLI в основном валидирует схему OpenAPI 3.0/Swagger 2.0 плюс базовые проверки (дубликаты, MIME-типы), но устарел и не развивается
При помощи валидации спецификации можно вовремя заметить ломающие изменения, но это не решает проблему неидеальности OpenAPI-спецификации.
2. Подготавливать OpenApi спецификацию
У Redocly CLI для модификации спецификации используется функция decorators, которая позволяет трансформировать документ (добавлять примеры, описания или изменять URL).
Decorators подходят для простых трансформаций (удаление эндпоинтов, добавление примеров), но для сложных случаев требуют custom plugins на JS, что усложняет поддержку.
Для упрощения подготовки OpenAPI-спецификации можно использовать openapi-modifier — он содержит широкий набор простых правил для трансформаций, которые применяются последовательно (собираются в pipeline). Таким образом можно строить сложные трансформации.
Пример конфигурации openapi-modifier:
module.exports = { input: "./openapi.yaml", output: "./openapi.mod.yaml", logger: { verbose: true }, pipeline: [ // JIRA-232: добавляем ручки новой фичи { rule: "merge-openapi-spec", config: { path: "docs/feature-JIRA-232.yaml" } }, // JIRA-8752: исправляем тип id, сохраняя остальные поля Pet { rule: "patch-component-schema", config: { descriptor: "Pet.id", patchMethod: "merge", schemaDiff: { type: "string", format: "uuid" } } }, // Нормализуем форматы перед изменением JSON-ответа { rule: "change-content-type", config: { map: { "*/*": "application/json" } } }, { rule: "filter-by-content-type", config: { disabled: ["multipart/form-data"] } }, // Исправляем статусы в ответе — массиве объектов без $ref { rule: "patch-endpoint-response-schema", config: { endpointDescriptor: "GET /v1/pets", code: "200", contentType: "application/json", correction: "[].status", patchMethod: "merge", schemaDiff: { enum: ["active", "archived"] } } }, // Нормализация под генераторы { rule: "remove-operation-id" }, { rule: "remove-min-items" }, // Убираем deprecated ручки { rule: "remove-deprecated", config: { showDeprecatedDescriptions: true } }, // Убираем внутренние ручки { rule: "filter-endpoints", config: { disabledPathRegExp: [/^/internal(?:/|$)/] } }, // Удаляем неиспользуемые компоненты { rule: "remove-unused-components", config: { printDeletedComponents: true } }, ],};
openapi-modifier позволяет держать спецификацию в пригодном для кодогенерации состоянии независимо от темпа и качества исходной документации, а знания об исправлениях хранить декларативно, прозрачно и воспроизводимо.
Типичные ситуации и как их решить:
Ниже — независимые примеры конфигураций. Имена моделей, полей и пути условные; замените их на свои. Входной и выходной файлы можно передать через CLI: npx openapi-modifier --input openapi.yaml --output prepared-openapi.yaml --config openapi-modifier.config.js.
-
Бэкенд указал неверный тип int вместо integer, что ломает кодогенерацию TS-типов или API-клиентов Пример конфигурации:
module.exports = { pipeline: [{ rule: 'patch-component-schema', config: { // Исправляем тип поля id в модели Pet. descriptor: 'Pet.id', patchMethod: 'merge', schemaDiff: { type: 'integer' }, }, }],};
-
Бэкенд допустил опечатку в формате поля, например указал
datetimeвместоdate-timeПример конфигурации:
module.exports = { pipeline: [{ rule: 'patch-component-schema', config: { // Исправляем формат даты в поле createdAt. descriptor: 'Pet.createdAt', patchMethod: 'merge', schemaDiff: { type: 'string', format: 'date-time' }, }, }],};
-
Бэкенд забыл указать
"type": "object"в схеме модели, что ломает кодогенерацию TS-типов или API-клиентов Пример конфигурации:
module.exports = { pipeline: [{ rule: 'patch-component-schema', config: { // Указываем пропущенный тип модели Pet. descriptor: 'Pet', patchMethod: 'merge', schemaDiff: { type: 'object' }, }, }],};
-
Бэкенд на проде находится под другой прокси, и нужно поменять basepath для всех endpoint в спецификации Пример конфигурации:
module.exports = { pipeline: [{ rule: 'change-endpoints-basepath', // /pets → /api/v1/pets; меняются ключи paths, а не servers. config: { map: { '/': '/api/v1/' } }, }],};
-
Мешают minItems и maxItems для генерации типов или API-клиентов Пример конфигурации:
module.exports = { pipeline: [ // Убираем ограничение на минимальную длину массивов. { rule: 'remove-min-items' }, // Убираем ограничение на максимальную длину массивов. { rule: 'remove-max-items' }, ],};
-
Мешает operationId для генерации типов или API-клиентов Пример конфигурации:
module.exports = { pipeline: [ // Удаляем operationId у всех операций. { rule: 'remove-operation-id' }, ],};
-
Нужно убрать лишние варианты ответов application/xml и переписать / на application/json для генерации типов или API-клиентов Пример конфигурации:
module.exports = { pipeline: [ { // Убираем XML из запросов и ответов. rule: 'filter-by-content-type', config: { disabled: ['application/xml'] }, }, { // Заменяем */* на application/json. rule: 'change-content-type', config: { map: { '*/*': 'application/json' } }, }, ],};
-
Не нужны все ручки из OpenAPI-спецификации, к примеру, нужно убрать /private/* ручки из документации для фронтенда. Это не ограничивает доступ к реальным эндпоинтам Пример конфигурации:
module.exports = { pipeline: [{ // Исключаем /private и все пути внутри /private/. rule: 'filter-endpoints', config: { disabledPathRegExp: [/^/private(?:/|$)/] }, }],};
-
Параметр или свойство метода скоро исчезнет (оно еще не помечено как deprecated в документации), и нужно отредактировать спецификацию, чтобы после перегенерации типов или API-клиентов TypeScript подсказал, где нужно убрать его использование, и дополнительно не допустить повторного его использования Пример конфигурации:
module.exports = { pipeline: [{ // Удаляем query-параметр version у GET /pets. rule: 'remove-parameter', config: { endpointDescriptor: 'GET /pets', parameterDescriptor: { name: 'version', in: 'query' }, }, }],};
-
Параметр или свойство метода скоро появится, и нужно добавить его, но в спецификации бэкенда его еще нет, а разработку фронтенда нужно начинать — нужно отредактировать спецификацию, чтобы после перегенерации типов или API-клиентов новый параметр появился в них Пример конфигурации:
module.exports = { pipeline: [{ rule: 'patch-component-schema', config: { descriptor: 'Pet', patchMethod: 'deepmerge', schemaDiff: { // Добавляем необязательное поле color, сохраняя остальные поля. properties: { color: { type: 'string' } }, }, }, }],};
Помимо данных правил, есть еще правила:
-
удаление deprecated компонентов, эндпоинтов, параметров и свойств (см. правило remove-deprecated) — позволяет вовремя замечать устаревания используемых элементов спецификации, например, бекендеры помечают элементы deprecated флагом — фронтендеры, замечая, что они пропадают с openapi вовремя замечают и съезжают с них
-
удаляющее неиспользуемые компоненты/cхемы, на которые нет ссылок или ref’ов (см. правило remove-unused-components) — позволяет очистить спецификацию от неиспользуемых элементов спецификации, к примеру, если были отфильтрованы ручки и остались неиспользуемые компоненты
-
объединение нескольких спецификаций (см. правило merge-openapi-spec) — полезно в ситуации, когда появляется новая фича, документация для ручек которой еще не готова для влития в основную, а параллельную разработку можно начинать.
Преимущества, которые даёт openapi-modifier:
-
Автоматизация — не нужно вручную править OpenAPI-файлы после каждого обновления
-
Исправление опечаток — быстрое исправление банальных ошибок без ожидания фикса от бэкенда
-
Документирование причин — каждое правило можно снабдить комментарием с номером задачи
-
Улучшение коммуникации в команде — единый источник правды
-
Ускорение онбординга новых разработчиков — самодокументируемый код, все причины правок документированы
-
Pipeline-подход — правила применяются последовательно, можно строить сложные трансформации. Любое правило можно применять несколько раз — дробите фиксы на мелкие осмысленные блоки с комментариями и ссылками на тикеты.
Источник: habr.com