Модуль 6 · Стандарт и его проверка

Справочник «Стандартизация HTTP API» · модуль 6 · Стандарт и его проверка

Глава 6.2

6.2

Линтер
Spectral

Часть правил стандарта видна прямо в описании OpenAPI, и проверять их может программа. Такая программа называется линтером: она читает файл контракта и сообщает о нарушениях. В этой главе разобран готовый набор правил, три собственных правила проекта и настоящий вывод линтера на испорченном описании.

Категория
Стандарт
Уровень
Средний
Связанные главы
6.1 API Style Guide · 6.3 Контрактные тесты · 6.4 CI
Зачем это знать
Чтобы правила стиля проверялись сами, а не глазами на ревью

§1Что такое линтер описания

Spectral читает файл описания OpenAPI и проверяет его по набору правил. Он не запускает сервис и не отправляет запросов — работает только с текстом контракта. Поэтому он проверяет только то, что записано в описании, и не видит того, что проявляется лишь в поведении сервиса.

запуск
npx @stoplight/spectral-cli@6.16.3 lint openapi/openapi.yaml --fail-severity=warn
вывод на исправном контракте учебного проекта
No results with a severity of 'warn' or higher found!

Обратите внимание на закреплённую версию @6.16.3: если её не указать, очередной выпуск линтера может добавить правило, и сборка упадёт без изменений в коде проекта. То же требование к внешним инструментам встречалось в главах 3.4 и 5.2.

§2Готовый набор spectral:oas

Писать правила с нуля не нужно: в Spectral встроен набор spectral:oas — десятки проверок для описаний OpenAPI. Он ловит и формальные ошибки (описание не соответствует схеме формата), и содержательные упущения.

info-contactВ разделе info заполнены контакты владельца API. Ровно то требование Zalando 218, о котором шла речь в главе 3.2.info
info-descriptionУ API есть описание, а не только название.info
operation-operationIdУ каждой операции задан operationId. Без него генераторы клиентов придумывают имена сами.операции
operation-tagsКаждая операция отнесена к группе, иначе она попадёт в безымянный раздел документации.операции
path-paramsПараметры пути объявлены и совпадают с тем, что написано в самом пути.пути
Репозиторий Spectral на GitHub: описание линтера для OpenAPI и JSON
Исходный проект. Spectral — открытый линтер для JSON и YAML с готовыми наборами правил для OpenAPI. Набор spectral:oas встроен: подключать его отдельно не нужно, достаточно строки extends.

В файле настроек проекта эти правила перечислены отдельно, чтобы повысить их уровень до error: включены они и по умолчанию, но в этом проекте отсутствие контактов считается ошибкой, а не предупреждением.

§3Файл настроек проекта

.spectral.yaml · начало
extends:
  - spectral:oas

rules:
  info-contact: error
  info-description: error
  operation-operationId: error
  operation-tags: error
  path-params: error

  # Собственные правила проекта из docs/API_STYLE_GUIDE.md
  …
extendsКакие готовые наборы взять за основу. Можно указать и чужой набор по адресу — так поступают компании, публикующие свои правила.основа
rulesИзменения: повысить или понизить уровень готового правила, выключить его или добавить своё.правки
комментарийСсылка на раздел стандарта, из которого взяты собственные правила. Связь документа и проверки не должна теряться.связь

§4Своё правило: из чего состоит

Собственное правило описывается четырьмя частями. Разберём их на правиле про имена путей.

.spectral.yaml · правило paths-no-verbs
paths-no-verbs:
  description: URI MUST описывать ресурс существительным, а не действием.
  severity: error
  given: "$.paths"
  then:
    field: "@key"
    function: pattern
    functionOptions:
      notMatch: "/(get|create|update|delete|remove|add)[A-Za-z]*(/|$)"
descriptionТекст, который увидит человек в выводе. Пишется так же, как строка стандарта, — со словом уровня требования.что нарушено
severityНасколько это серьёзно: error, warn, info, hint.насколько важно
givenКуда смотреть. Выражение JSONPath по документу описания: здесь — раздел paths целиком.где искать
thenЧто проверять и какой проверкой. Здесь: взять ключ (@key — сам адрес) и убедиться, что он не совпадает с образцом из глаголов.что проверять
Про @key

Особое значение @key означает «проверяй не значение, а имя поля». Это нужно там, где смысл несёт именно имя: адрес в paths, название свойства в схеме. Без него правило проверяло бы содержимое, а не заголовок.

§5Три правила учебного проекта

1 · ошибки отвечают в формате Problem Details
error-responses-use-problem-json:
  description: Ответы 4xx и 5xx MUST использовать application/problem+json.
  severity: error
  given: "$.paths[*][*].responses[?(@property.match(/^[45]/))]"
  then:
    field: content
    function: schema
    functionOptions:
      schema:
        type: object
        required: [application/problem+json]

Выражение в given читается так: во всех путях, во всех операциях, среди ответов взять те, чей код начинается с 4 или 5. Дальше у каждого проверяется, что в content есть ключ application/problem+json. Это правило из главы 4.1, доведённое до автоматической проверки.

2 · свойства JSON в snake_case
json-properties-snake-case:
  description: Свойства JSON MUST быть записаны в snake_case.
  severity: error
  given: "$.components.schemas[*].properties"
  then:
    field: "@key"
    function: casing
    functionOptions:
      type: snake

Здесь используется готовая проверка casing — писать образец вручную не нужно. Правило проверяет имена свойств всех схем сразу, поэтому единообразие не приходится отслеживать вручную.

3 · адреса без глаголов
paths-no-verbs:  # разобрано в §4

Трёх собственных правил для учебного проекта достаточно. Набор пополняют тогда, когда одно и то же замечание повторяется на ревью: повторяющееся замечание переводят в правило.

§6Настоящий вывод на плохом контракте

Проверим, что правила работают. Возьмём описание учебного сервиса и намеренно испортим его в двух местах: переименуем created_at в createdAt и заменим адрес /api/v1/tickets/open на /api/v1/getTickets.

терминал
npx @stoplight/spectral-cli@6.16.3 lint bad.yaml --fail-severity=warn
настоящий вывод
bad.yaml
 209:22  error  paths-no-verbs              URI MUST описывать ресурс существительным,
                                            а не действием.  paths./api/v1/getTickets
 581:19  error  json-properties-snake-case  Свойства JSON MUST быть записаны в snake_case.
                                            components.schemas.Ticket.properties.createdAt

✖ 2 problems (2 errors, 0 warnings, 0 infos, 0 hints)
  1. Строка и столбец. 209:22 — точное место в файле, можно открыть и поправить.
  2. Уровень и имя правила. По имени правило находится в .spectral.yaml, а оттуда — в стандарте.
  3. Текст описания. Тот же, что был записан в правиле: человек сразу видит, что именно нарушено.
  4. Путь внутри документа. components.schemas.Ticket.properties.createdAt — где в структуре описания сидит нарушение.
  5. Итог. Две ошибки — значит, ненулевой код возврата, и сборка не пройдёт.
Чем это отличается от замечания на ревью

Линтер выдаёт результат сразу после запуска и применяет одни и те же правила к любому изменению. Замечание человека приходит позже и может быть пропущено. За счёт этого на ревью остаются вопросы, которые проверить программой нельзя.

§7Уровни и порог падения

У каждого правила есть уровень, а у запуска — порог, начиная с которого проверка считается провалившейся.

УровеньСмыслСоответствие стандарту проекта
errorНарушение обязательного правилаMUST
warnНарушение рекомендацииSHOULD
infoЗамечание к сведению—
hintПодсказка, как сделать лучшеMAY

В учебном проекте запуск идёт с ключом --fail-severity=warn: падает не только на ошибках, но и на предупреждениях. Порог выбран строгий: описание учебного стенда небольшое, и держать его без замечаний проще, чем вести список известных предупреждений.

Строгий порог

--fail-severity=warn. Годится, когда описание маленькое или проект молодой. Держит контракт в чистоте с первого дня.

Мягкий порог

--fail-severity=error. Нужен большому существующему API: сначала останавливают появление новых ошибок, потом постепенно вычищают предупреждения.

§8Чего линтер не проверяет

Ограничение то же, что у oasdiff из главы 5.2: линтер видит описание, а не поведение.

Правило стандартаПроверяет линтер?Чем проверяется
Адреса без глаголовдаpaths-no-verbs
Свойства в snake_caseдаjson-properties-snake-case
Ошибки в формате Problem Details в описаниидаerror-responses-use-problem-json
Сервис действительно отвечает Problem Detailsнеттесты поведения, глава 6.3
201 приходит с заголовком Locationнеттесты поведения
Выгруженный файл совпадает с кодомнетexport_openapi.py --check
Изменение не ломает клиентовнетoasdiff, глава 5.2

Этим определяется состав сборки, разобранный в главе 6.4: линтер, тесты и сравнение версий — три проверки, каждая из которых закрывает свою часть стандарта.

§9Частые ошибки

Запускать линтер без закреплённой версииНовая версия добавит правило, и сборка упадёт без изменений в вашем коде.
Правило без понятного descriptionЧеловек видит имя правила и не понимает, что от него хотят.
Копить предупрежденияЧем длиннее список «известных» замечаний, тем меньше внимания обращают на вывод линтера в целом.
Считать зелёный линтер доказательством правильности APIОн проверил описание, а не поведение сервиса.
Правила в линтере есть, а в стандарте нетТогда проверка живёт отдельно от документа, и её решения нечем обосновать.

§10Проверьте себя

  1. Что читает Spectral и почему он не может проверить, что сервис вернул 201 с Location?
  2. Из каких четырёх частей состоит своё правило? Объясните назначение каждой.
  3. Что означает @key в поле field и почему без него правило про snake_case не сработает?
  4. Прочитайте выражение $.paths[*][*].responses[?(@property.match(/^[45]/))] словами.
  5. Чем отличается запуск с --fail-severity=warn от --fail-severity=error и когда какой выбирают?
  6. Назовите три правила стандарта, которые линтер проверить не может, и скажите, чем они проверяются.
Запускspectral-cli@6.16.3 lint--fail-severity=warn
Своё правилоdescriptionseveritygiven (JSONPath)then (функция)
Правила проектаerror-responses-use-problem-jsonjson-properties-snake-casepaths-no-verbs
Границыописание — даповедение — нет

Справочник «Стандартизация HTTP API» · модуль 6 · глава 6.2Макаров Максим Николаевич

Кадр