OpenAPI/Swagger для начинающих
Для кого эта статья
В основном для аналитиков, которые впервые сталкиваются с необходимостью описания запросов в Swagger, но может быть полезна всем, кто хочет разобраться или ищет подсказку как описывать запросы в этом формате.
Плюсы
Если вы в первый раз решили использовать OpenAPI/Swagger, то может показаться, что это слишком сложно в плане синтаксиса, но плюсы перевешивают. Давайте их обозначим:
Для большого количества методов с повторяющимися элементами OpenAPI позволяет в итоге ускорить описание
OpenAPI дает более широкие возможности описания запросов и их элементов, чем любая таблица в Confluence.
Swagger можно использовать при тестировании.
Из готового документа можно сгенерировать сервер и клиент.
Я часто встречал мнение, что OpenAPI/Swagger подходит только для генерации из кода, и что синтаксис для написания руками перегружен для большинства джунов-аналитиков. Но, на мой взгляд, это не так: этот формат, древовидный и хорошо задокументированный, изучать чуть сложнее, чем json.
Концепция
Документ YAML OpenAPI представляет собой дерево с набором ветвей. Есть несколько базовых ветвей, таких как запросы или компоненты. Они в свою очередь также могут содержать дополнительные ветви, содержащие описание операций, параметры запросов и схемы ответов.
Важно, что можно ссылаться на существующие объекты с помощью указания путей до них через $ref, например: "#/components/schemas/AllOfExampleGet". И это одно из ключевых преимуществ: какие-то элементы могут использоваться повторно.
Чтобы начать описывать запросы, достаточно освоить два основных раздела: запросы и компоненты.
Компоненты
Начнем с компонентов, основных кирпичиков, позволяющий вынести за пределы конкретных запросов однотипные описания. Вынос описания в отдельные компоненты дает ряд преимуществ:
уменьшает копипаст и длину кода;
позволяет одновременно редактировать сразу несколько запросов через их общий компонент.
В компоненты можно вынести:
schemas: определяет структур данных (JSON Schema), которые могут использоваться в теле запросов и ответов;
responses: определяет возможные ответы;
parameters: определяет параметры запросов;
examples: примеры данных;
requestBodies: определяет тело запросов;
headers: определяют заголовки;
securitySchemes: определяет схему безопасности, такие как API ключи, токены и др.;
links: определяет связь между запросами для перехода между ресурсами;
callbacks: определяет обратные вызовы, которые описывают, как ваш API будет уведомлять клиента.
Чаще всего используются schemas и responses. Schemas позволяет описать повторяющиеся ответы и тела запросов, а responses − вынести описание ошибок (200 ответы тоже можно вынести, но чаще всего они отличаются от запроса к запросу). Headers, securitySchemes, parameters − вторые по частоте использования в документации, которую я видел.
Schemas
Schemas позволяет управлять шаблонными структурами данных. Схема содержит в себе отдельные классы, которые содержат в себе описание входящих в них элементов. Например:
Category:
type: object
properties:
id:
type: integer
format: int64
example: 1
name:
type: string
example: Dogs
xml:
name: category
Это класс Category с типом объекта, у которого есть свойства: id, name. Для последних также указан тип, формат и пример значения.
Type указывает на то, какие еще параметры ожидаются для этого элемента. Type может принимать следующие значения:
Type может принимать следующие значения:
"string" (строка): для текстовых данных;
"number" (число): для числовых данных, включая как целые числа, так и числа с плавающей точкой;
"integer" (целое число): для целочисленных данных;
"boolean" (булев тип): для логических значений true или false;
"object" (объект): для структурированных данных, например, объектов JSON;
"array" (массив): для упорядоченного списка элементов;
"null" (пусто): для отсутствующего значения.
В случае, если это не object или array, данный элемент будет обычным параметром со значением. Заполнение класса такого типа не будет отличаться от заполнения любого другого параметра. Поэтому важнее рассмотреть object и array, которые используются чаще всего.
Object и array позволяют задавать сложные структуры, как это было показано выше для Category. При создании массива нужно дополнительно указать элемент разметки – items. Например, так:
type: array
items:
type: object
properties:
name:
type: string
age:
type: integer
email:
type: string
format: email
Список элементов
Значения, которые можно установить для каждого из используемых элементов:
Значения элементов:
title: имя свойства;
description: рредоставляет подробное описание свойства;
default: задает значение по умолчанию для свойства, если оно не предоставлено;
maximum и minimum: указывает максимальное и минимальное значение числового свойства;
exclusiveMaximum и exclusiveMinimum: указывает, являются ли максимальное и минимальное значения исключительными или включительными;
maxLength и minLength: указывает максимальную и минимальную длину строкового свойства;
pattern: применяет валидацию строки на основе регулярного выражения;
format: указывает формат данных. Общие форматы включают "email", "date", "uri" и т. д.
string (строка):
format: date-time - строка с датой и временем в формате RFC 3339.
format: date - строка с датой в формате YYYY-MM-DD.
format: password - строка, предназначенная для пароля.
number (число):
format: float - десятичное число с плавающей точкой (32 бита).
format: double - десятичное число с плавающей точкой (64 бита).
format: int32 - 32-битное целое число.
format: int64 - 64-битное целое число.
integer (целое число):
format: int32 - 32-битное целое число.
format: int64 - 64-битное целое число.
boolean (булев тип):
array (массив):
items - определение типа элементов массива.
object (объект):
properties - определение свойств объекта.
null (пусто):
nullable: позволяет свойству принимать значение null.
readOnly и writeOnly: указывает, является ли свойство доступным только для чтения или только для записи.
example: предоставляет примерное значение для свойства.
enum: указывает список допустимых значений для свойства.
items: описывает свойства элементов в массиве.
uniqueItems: указывает, должны ли элементы в массиве быть уникальными.
$ref: предоставляет ссылку на внешнее определение.
Этот список я рекомендую запомнить лучше всего, так как он позволяет наиболее точно описать требования к элементам класса.
Запросы
В запросе можно указать следующий набор свойств:
summary: краткое описание операции;
description: подробное описание операции;
tags: список тегов, к которым относится операция;
parameters: список параметров, необходимых для выполнения операции;
responses: список возможных ответов от сервера;
requestBody: описание тела запроса.
С summary все просто, оно отображается рядом с названием метода в свернутом виде. С description все чуть сложнее, в него можно засунуть не просто текстовое описание, markdown. Например, такой:
description: >-
Метод отвечает за получение данных о пользователе
* Если пользователь не найден, нужно вернуть ошибку
* Если пользователь удален, нужно вернуть ошибку
Поэтому можно описывать какие-то важные вещи прямо в описании запроса.
Tags позволяют разметить, в какой блок должен быть помещен запрос. Например, вы делаете запросы про информацию о магазине. Укажите у них одинаковый тег, и они будут сгруппированы в одном блоке. Опционально описание тегов можно задать в отдельном разделе описания всей документации.
parameters имеет следующую структуру:
/users/{userId}:
get:
parameters:
- name: userId
in: path
required: true
description: "ID of the user"
allowEmptyValue: true
schema:
type: integer
Параметры могут быть указаны для path, query, header и cookie. Вместо schema может быть указан content для передачи сложного json’а.
Для запросов, которые поддерживают тело запроса, указывается requestBody. Все просто: указываем обязательность, описание, тип и схему контента. Также можно указать сразу на компонент из requestBodies (на практике чаще всего используется схема, так как тело запроса редко остается неизменным).
С responses тоже все относительно понятно: это список ответов на запросы, стандартные ошибки удобно вынести в компоненты и переиспользовать.Конструкции
Конструкции anyOf, oneOf, allOf
С anyOf, oneOf и вы можете указать, например, несколько вариантов тела запроса, которые он может принимать. Встречал довольно редко.
А вот allOf − это суперудобная вещь: с ее помощью можно объединить несколько данных в одну сущность. И это позволяет, например, сложить несколько компонентов в один. Чуть ниже будет пример.
Лайфхаки
allOf
Если есть набор запросов на одну сущность, например, книги, удобнее всего описывать schemas в порядке увеличения элементов через их объединение с помощью конструкции allOf. Например, у нас есть GET-запрос, который возвращает имя и возраст пользователя. Позже у нас появляется потребность сделать POST-запрос, который добавляет еще один параметр в запросе – address. Это можно сделать через создание нового класса и объединения в нем AllOfExampleGet с новым параметром – address.
AllOfExampleGet:
type: object
properties:
name:
type: string
age:
type: integer
AllOfExamplePost:
type: object
allOf:
- $ref: "#/components/schemas/AllOfExampleGet"
properties:
address:
type: integer
required:
- name
Required-поля
В примере выше можно заметить, что обязательность name была добавлена в новой схеме. И это суперудобно! Можно копипастить один и тот же класс в другие, расставляя новые требования к обязательности полей. Обязательность полей
Обязательность можно указать для конкретной схемы. Например, у есть запрос на запись и возврат, и на post запрос мы хотим сделать обязательными ряд полей.
ChatGPT
ChatGPT хорошо переводит описания таблиц в компоненты: удобно скопировать из приложения управлением базами (DBeaver/DataGrip) содержание таблицы и попросить сформировать компоненты. Это сильно сокращает время на запросы, которые просто вытаскивают или засовывают данные из таблиц и в них.
С чего начинать описание
Я рекомендую начинать описание с компонентов, если уже есть понимание, какие запросы планируется реализовать. Это позволит сразу описать повторяющиеся элементы и сократит размер кода.
Description
Я рекомендую добавлять в описание запросов всю информацию, которая туда может только влезть. Указывать ссылку на Сonfluence, где описывается сценарий, прописывать неочевидную логику того, как у вас проверяются поля, в какую таблицу делается запись.
Инструменты
Лично мне удобнее писать в IDE. Прикладываю ссылки на VS Code. Но онлайн-эдитор тоже удобный. И там, и там подсвечиваются ошибки с довольно точным указанием на исправление.
https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml
https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi
Шаблоны
Подсмотреть, как описывается тот или иной элемент можно в шаблоне на https://editor.swagger.io/.
Также я сделал отдельный шаблон, который может помочь при копипасте похожих элементов + в качестве небольшой подсказки: https://github.com/AlexUra/swagger-openapi-example/blob/main/example.yml
P.S. Еще у меня канал есть