swagger-coverage
Полное покрытие OpenAPI/Swagger для всех роутов бэкенда — детект стека (Fastify/Express/NestJS/Koa/Hapi/tRPC, статические спеки, не-JS), аудит непокрытых/частичных эндпоинтов и их корректное покрытие.
SKILL.mdskills/swagger-coverage/SKILL.md
GitHubНавык: OpenAPI/Swagger Coverage
Каждый роут бэкенда должен быть полностью описан в OpenAPI-спеке. Спека — источник истины для фронт-клиента (скилл data-fetching).
Подход фреймворк-агностичен: сперва определи стек и источник спеки, затем применяй универсальный чек-лист операции. Не предполагай Fastify по умолчанию — сначала посмотри, чем реально объявлены роуты. Ниже подробный дефолт-пример (Fastify + zod), плюс детект и эвристики под другие стеки.
Шаг 0. Детект стека и источника спеки
Определи по package.json (зависимости/скрипты), импортам и файлам, чем объявлены роуты и откуда берётся спека:
| Стек | Как объявлены роуты | Где спека |
|---|---|---|
Fastify + @fastify/swagger |
app.get/post(url, { schema }) |
GET /documentation/json, app.swagger() |
Express + swagger-jsdoc |
router.get/post, JSDoc @openapi/@swagger |
генерируемый объект, swagger-ui-express |
Express/любой + tsoa |
декораторы @Route/@Get/@Post/@Response/@Security |
tsoa spec → swagger.json |
NestJS + @nestjs/swagger |
@Controller,@Get/@Post,@ApiOperation,@ApiResponse,@ApiTags |
GET /api-json (SwaggerModule) |
Koa + @koa/router (koa-oas3/koa2-swagger) |
router.get/post |
статич. спека или эндпоинт |
Hapi + hapi-swagger |
server.route({ method, path, options }) |
GET /swagger.json |
tRPC + trpc-openapi |
процедуры + .meta({ openapi }) |
generateOpenApiDocument(...) |
| Статическая спека | — (спека ведётся вручную) | openapi.{yaml,json} / swagger.{yaml,json} в репо |
| Не-JS | FastAPI (типы/Pydantic), Spring @Operation/springdoc, DRF drf-spectacular, Go swaggo аннотации |
/openapi.json, /v3/api-docs, /api/schema/ |
Если спека нигде не отдаётся и не генерируется — это уже первая «дыра»: API не документирован вовсе; зафиксируй как находку и предложи подключить генератор/спеку под стек.
Универсальный чек-лист: когда операция «полностью покрыта»
Не зависит от стека — применяй к каждой операции (path+method) в спеке:
- операция вообще присутствует в спеке (path+method из кода есть в
paths); -
summary— осмысленный, одна строка, не заглушка; -
description— что делает, побочные эффекты, ограничения; не «TODO», не копия summary; -
tags— группировка (обычно по модулю/ресурсу); -
operationId— стабильный уникальныйcamelCase(влияет на имена методов сгенерированного клиента); - параметры
path/query/headerтипизированы, где применимо; -
requestBodyтипизирован дляPOST/PUT/PATCH; -
responsesпо каждому реально возвращаемому статусу: успех (200/201/204) + все ошибки (400/401/403/404/409/422/500) через общий Error-компонент; -
securityна всех защищённых роутах; -
examples/deprecated— по месту.
Правило: покрытие ≠ ослабление. Не прячь роут из спеки (hide/exclude), не заменяй схемы ответа на «any»/пустые ради «зелёного» покрытия.
Эвристики поиска роутов (ripgrep, по стекам)
Найди объявления роутов способом под стек, затем сопоставь со спекой:
# Fastify / Express / Koa (роутеры)
rg -n "\.(get|post|put|patch|delete)\(|\.route\(|app\.route\(" src
# NestJS
rg -n "@(Get|Post|Put|Patch|Delete)\(|@Controller\(|@ApiOperation|@ApiResponse|@ApiTags" src
# tsoa
rg -n "@(Route|Get|Post|Put|Patch|Delete|Response|Security|Tags)\(" src
# Hapi
rg -n "server\.route\(|method:\s*['\"](GET|POST|PUT|PATCH|DELETE)" src
# tRPC + trpc-openapi
rg -n "\.meta\(\s*\{\s*openapi|publicProcedure|protectedProcedure" src
# защищённые роуты (любой стек)
rg -n "requireAuth|verifyJwt|onRequest|@Security|UseGuards|preHandler" src
Для каждого найденного роута проверь метаданные документации механизмом стека: schema (Fastify), @Api*-декораторы (Nest), JSDoc @openapi (swagger-jsdoc), декораторы (tsoa), options.tags/validate/response (Hapi), .meta.openapi (tRPC). Затем сверь множество путей роутов с paths фактической спеки — расхождение или неполнота = дыра.
Сигналы «дыр» (универсально): роут есть в коде, но нет в спеке; операция без summary/description/tags/operationId; responses только для успеха, без ошибок; защищённый роут без security; публичный роут скрыт/исключён из спеки; рассинхрон схемы ответа с тем, что реально отдаёт хендлер.
Дефолт-стек: Fastify + zod (единый источник правды)
Если стек — Fastify, лучший вариант: одни zod-схемы дают И валидацию, И OpenAPI — через fastify-type-provider-zod (validatorCompiler/serializerCompiler + jsonSchemaTransform; jsonSchemaTransformObject + z.globalRegistry.add(Schema,{id}) для $ref-компонентов). Альтернатива — TypeBox + @fastify/type-provider-typebox.
swagger.plugin.ts:
import fp from 'fastify-plugin'
import fastifySwagger from '@fastify/swagger'
import fastifySwaggerUI from '@fastify/swagger-ui'
import {
jsonSchemaTransform, jsonSchemaTransformObject,
serializerCompiler, validatorCompiler,
} from 'fastify-type-provider-zod'
export const swaggerPlugin = fp(async (app) => {
app.setValidatorCompiler(validatorCompiler)
app.setSerializerCompiler(serializerCompiler)
await app.register(fastifySwagger, {
openapi: {
openapi: '3.1.0',
info: { title: 'API', version: env.APP_VERSION },
components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } } },
},
transform: jsonSchemaTransform,
transformObject: jsonSchemaTransformObject,
})
await app.register(fastifySwaggerUI, { routePrefix: '/documentation' })
})
schemas.ts (общий error-компонент + схемы модуля):
import { z } from 'zod'
export const ErrorSchema = z.object({ error: z.string(), message: z.string(), code: z.string().optional() })
z.globalRegistry.add(ErrorSchema, { id: 'Error' }) // → components.schemas.Error
export const UserParams = z.object({ id: z.string().uuid() })
export const UserResponse = z.object({ id: z.string().uuid(), email: z.string().email(), createdAt: z.string().datetime() })
routes.ts с полной schema:
app.withTypeProvider<ZodTypeProvider>().get('/users/:id', {
onRequest: [requireAuth],
schema: {
summary: 'Получить пользователя по id',
description: 'Возвращает публичный профиль. 404 если не найден.',
tags: ['users'], operationId: 'getUserById',
security: [{ bearerAuth: [] }],
params: UserParams,
response: { 200: UserResponse, 401: ErrorSchema, 404: ErrorSchema, 500: ErrorSchema },
},
}, usersController.getById)
Другие стеки — тот же результат иным механизмом: NestJS — @ApiOperation/@ApiResponse/@ApiTags + DTO с @ApiProperty; Express — JSDoc @openapi (swagger-jsdoc) или tsoa-декораторы; Hapi — options.validate/response/tags; статическая спека — правки прямо в openapi.yaml.
Как полноценно ПОКРЫТЬ (write)
По одному роуту, механизмом стека:
- Опиши вход (
params/query/headers/body) и все ответы (успех + ошибки), заведи/переиспользуй общий Error-компонент. - Проставь
summary/description/tags/operationId; для защищённых —security. - Сверь хендлер/controller: он возвращает РОВНО то, что в схеме ответа, с верными статусами. Расхождение — чини хендлер или схему, не ослабляя валидацию.
- Не прячь роут (
hide/exclude) и не ставьany/пустые схемы ради покрытия.
Проверка (verification)
- Получи спеку: рантайм-эндпоинт стека (
/documentation/json,/api-json,/openapi.json,/v3/api-docs, …), статический файл, или генератор (tsoa spec,generateOpenApiDocument, сборка Nest/FastAPI). - Провалидируй:
npx @redocly/cli lint <spec>(илиswagger-cli validate <spec>). - Убедись, что каждый роут присутствует с полным operation-объектом (
operationId,responsesпо статусам,securityгде нужно). - Сгенерируй типы фронта без ошибок:
npx openapi-typescript <spec> -o src/shared/api/schema.d.ts. - Прогони тесты (ответы совпадают со схемами).
Связь с фронтендом
Эта спека — источник истины для фронтового клиента (скилл data-fetching): фронт всегда бьёт в реальные эндпоинты, типы генерируются из OpenAPI, моков в прод-пути нет.