Для кого эта статья

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

Плюсы

Если вы в первый раз решили использовать OpenAPI/Swagger, то может показаться, что это слишком сложно в плане синтаксиса, но плюсы перевешивают. Давайте их обозначим:

  1. Для большого количества методов с повторяющимися элементами OpenAPI позволяет в итоге ускорить описание

  2. OpenAPI дает более широкие возможности описания запросов и их элементов, чем любая таблица в Confluence.

  3. Swagger можно использовать при тестировании.

  4. Из готового документа можно сгенерировать сервер и клиент.

Я часто встречал мнение, что 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

Список элементов

Значения, которые можно установить для каждого из используемых элементов:

Значения элементов:
  1. title: имя свойства;

  2. description: рредоставляет подробное описание свойства;

  3. default: задает значение по умолчанию для свойства, если оно не предоставлено;

  4. maximum и minimum: указывает максимальное и минимальное значение числового свойства;

  5. exclusiveMaximum и exclusiveMinimum: указывает, являются ли максимальное и минимальное значения исключительными или включительными;

  6. maxLength и minLength: указывает максимальную и минимальную длину строкового свойства;

  7. pattern: применяет валидацию строки на основе регулярного выражения;

  8. format: указывает формат данных. Общие форматы включают "email", "date", "uri" и т. д.

    1. string (строка):

      1. format: date-time - строка с датой и временем в формате RFC 3339.

      2. format: date - строка с датой в формате YYYY-MM-DD.

      3. format: password - строка, предназначенная для пароля.

    2. number (число):

      1. format: float - десятичное число с плавающей точкой (32 бита).

      2. format: double - десятичное число с плавающей точкой (64 бита).

      3. format: int32 - 32-битное целое число.

      4. format: int64 - 64-битное целое число.

    3. integer (целое число):

      1. format: int32 - 32-битное целое число.

      2. format: int64 - 64-битное целое число.

    4. boolean (булев тип):

    5. array (массив):

    6. items - определение типа элементов массива.

    7. object (объект):

    8. properties - определение свойств объекта.

    9. null (пусто):

  9. nullable: позволяет свойству принимать значение null.

  10. readOnly и writeOnly: указывает, является ли свойство доступным только для чтения или только для записи.

  11. example: предоставляет примерное значение для свойства.

  12. enum: указывает список допустимых значений для свойства.

  13. items: описывает свойства элементов в массиве.

  14. 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://editor.swagger.io/

Также я сделал отдельный шаблон, который может помочь при копипасте похожих элементов + в качестве небольшой подсказки: https://github.com/AlexUra/swagger-openapi-example/blob/main/example.yml

P.S. Еще у меня канал есть

Комментарии (2)


  1. yunus_mil
    27.11.2023 11:17
    +1

    Классная статья, сам неоднократно пользовался сваггер и по опыту знаю насколько удобно это для разработчиков и аналитиков. Спасибо автору, освежил знания )


  1. cherkalexander
    27.11.2023 11:17
    +1

    Как указано в статье, OpenAPI схема позволяет генерировать клиентский код. Это может быть полезно, когда сервис есть, а библиотеки у него нет. Я так например генерировал клиента для Яндекс Музыки. Описал их Open API схему и сгенерировал JS библиотеку. https://github.com/acherkashin/yandex-music-open-api/blob/main/src/yandex-music.yaml

    Ну и можно генерировать аналогично библиотеку, чтобы ходить на свой бэк. Генерируешь схему для методов бэка, а из схемы клиентский код на любом языке.