Сервис задач ещё не реализован, но разработчику клиентского приложения и тестировщику уже нужно договориться, какие запросы он будет принимать. Без описания интерфейса легко разойтись в деталях: один ожидает статус 201 после создания, другой — 200, а поля ответа каждый трактует по-своему.
OpenAPI задаёт формат описания HTTP API. Мы создадим openapi.yaml для небольшого Tasks API: получение списка задач, создание задачи и чтение по идентификатору. Получится цельный YAML-документ с параметрами, телом запроса, ответами и схемами JSON. Затем проверим структуру спецификации и пример ответа.
Вам потребуются базовые знания HTTP и JSON, текстовый редактор и Python с библиотеками для проверки. Сервер, база данных и аккаунт во внешнем сервисе не нужны. Адрес API в примере демонстрационный: это описание будущего интерфейса, а не готовый работающий сервис.
Фразу «Swagger YAML» иногда используют для файла OpenAPI по старому названию спецификации. В этом руководстве мы работаем с самим документом; историю названий и сравнение Swagger UI, Editor и Codegen оставим за его пределами.
Создайте начало YAML-документа
В OpenAPI 3.1 документ можно записать в YAML или JSON. Выберем YAML: его вложенные объекты удобно читать без фигурных скобок. Создайте пустой openapi.yaml и запишите:
openapi: 3.1.0
info:
title: Tasks API
version: '1.0.0'
description: Учебное описание REST API для задач.
openapi задаёт версию формата, а info.version — версию самого описания API. Первая помогает инструментам правильно читать документ. Вторую команда определяет для своего интерфейса; например, её изменение не переключает синтаксис OpenAPI. Мы намеренно используем версию 3.1.0, хотя в официальном каталоге есть и более новые редакции.
Теперь зададим базовый адрес, на который по контракту будут отправляться запросы:
servers:
- url: https://example.com
При добавлении пути /tasks предполагаемый полный адрес получится путём объединения базового URL и пути. Домен example.com в примере демонстрационный, поэтому отправлять на него запросы ради проверки Tasks API не нужно. Поле servers описывает адрес, но не запускает сервер.
В нормативном документе OAS 3.1 поля openapi и info обязательны на верхнем уровне. Для YAML важна вложенность: title, version и description принадлежат info, а servers стоит на одном уровне с ним. В следующем разделе добавим paths на том же уровне.
Опишите пути и HTTP-операции
Нам нужны три действия: GET /tasks возвращает список задач, POST /tasks создаёт запись, а GET /tasks/{taskId} ищет задачу по идентификатору. В OpenAPI пути размещаются в paths, а HTTP-методы — внутри каждого пути. Для каждой операции зададим summary, operationId и responses. Входные данные укажем в parameters или requestBody там, где они необходимы.
Параметры запроса
Для списка добавим необязательный фильтр статуса. Поместите блок parameters внутри операции get для пути /tasks:
paths:
/tasks:
get:
parameters:
- name: status
in: query
required: false
schema:
type: string
enum: [open, done]
Показана часть операции: обязательный блок responses появится в полном документе ниже. Поле in: query означает параметр строки запроса, например ?status=open, а enum ограничивает его значение двумя строками. Значение required: false позволяет не передавать фильтр, но не определяет, какие задачи в этом случае вернёт сервер. Это поведение нужно согласовать отдельно.
В GET /tasks/{taskId} идентификатор размещается прямо в пути: /tasks/7. Параметр taskId находится в пути и поэтому объявлен с required: true. Это обязательное правило для параметров in: path в OAS 3.1. Схема параметра дополнительно требует целое число не меньше единицы.
Тело запроса и ожидаемые ответы
requestBody описывает тело HTTP-запроса, а responses — ответы операции. В POST /tasks мы укажем обязательное тело application/json и схему CreateTask: клиент передаёт название и необязательное описание. Ответ 201 будет содержать созданную задачу.
Для чтения ожидаем 200. В качестве ошибок выберем 400 при неверном запросе создания и 404 при отсутствующей задаче. Это наши договорённости для учебного API, а не результаты запроса к серверу. В responses кодами служат строковые ключи — '200', '201', '400', '404', — поэтому заключаем их в кавычки. У каждого ответа будет description; если ожидается JSON, дополнительно укажем content.application/json.schema.
В Swagger UI эта связь видна как одна операция: обязательное тело POST /tasks показано рядом с вариантами ответов 201 и 400. Интерфейс построен из готового YAML и подтверждает, что документ читается визуализатором; работающего HTTP-сервера за снимком нет.

Полный блок paths появится в готовом примере ниже. Разделяйте уровни: путь определяет адрес, метод — действие, а параметры, тело и ответы — его вход и выход.
Вынесите повторяющиеся модели в components.schemas
При чтении и создании задачи используется одна сущность. Вместо повторения всех полей в каждом ответе объявим три схемы:
Task— результат чтения или создания задачи сid,titleиstatus;CreateTask— вход создания с обязательнымtitleи необязательнымdescription;ApiError— JSON-ошибка с полямиcodeиmessage.
$ref позволяет сослаться на схему, объявленную в components.schemas. Например:
schema:
$ref: '#/components/schemas/Task'
Путь после # ведёт к объекту внутри этого же файла: components → schemas → Task. Такой ссылкой воспользуются успешные ответы GET /tasks/{taskId} и POST /tasks. Для списка задач схема будет массивом (type: array), а его items сошлются на Task.
Внутри Task отдельно действуют два правила. required: [id, title, status] означает, что эти свойства должны присутствовать. В свою очередь, type: integer для id требует число: строка "7" не пройдёт проверку. Значение status ограничим перечислением open или done.
В OpenAPI 3.1 type: [string, 'null'] описывает значение, которое может быть строкой или null. Мы применим это к description. Поскольку его нет в required, отсутствие поля также допустимо; но отсутствие обязательного title недопустимо. Это разные свойства схемы.
OAS 3.1 использует возможности JSON Schema Draft 2020-12, включая required, enum и массив типов. Семантика Schema Object и локальных ссылок описана в OpenAPI 3.1. Описания схем здесь ограничивают форму данных, но не определяют авторизацию и предметные правила сервиса.
Полный пример openapi.yaml
Объединим метаданные, адрес сервера, пути и схемы. Ниже полный YAML-документ, который можно скопировать в examples/openapi.yaml (или openapi.yaml в собственном проекте). Он самодостаточен: все ссылки ведут к схемам этого же файла.
openapi: 3.1.0
info:
title: Tasks API
version: '1.0.0'
description: Учебное описание REST API для задач.
servers:
- url: https://example.com
paths:
/tasks:
get:
summary: Получить список задач
operationId: listTasks
parameters:
- name: status
in: query
required: false
schema:
type: string
enum: [open, done]
responses:
'200':
description: Список задач
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Task'
post:
summary: Создать задачу
operationId: createTask
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTask'
responses:
'201':
description: Задача создана
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
'400':
description: Некорректный запрос
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
/tasks/{taskId}:
get:
summary: Получить задачу по идентификатору
operationId: getTask
parameters:
- name: taskId
in: path
required: true
schema:
type: integer
minimum: 1
responses:
'200':
description: Задача найдена
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
'404':
description: Задача не найдена
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
components:
schemas:
Task:
type: object
required: [id, title, status]
properties:
id:
type: integer
title:
type: string
status:
type: string
enum: [open, done]
description:
type: [string, 'null']
CreateTask:
type: object
required: [title]
properties:
title:
type: string
minLength: 1
description:
type: [string, 'null']
ApiError:
type: object
required: [code, message]
properties:
code:
type: string
message:
type: string
Проверьте две связи. Для GET /tasks поле items указывает на Task, поэтому каждый элемент массива должен иметь поля этой схемы. Тело POST /tasks ссылается на CreateTask, где идентификатора ещё нет. Имена после #/components/schemas/ должны совпадать с ключами схем с учётом регистра.
Swagger UI показывает три операции и три повторно используемые схемы из этого YAML. Снимок подтверждает отображение описания, но не работу сервера:

Проверьте спецификацию и JSON-ответ
Разбор YAML и проверка OpenAPI решают разные задачи. YAML-парсер может успешно прочитать файл, в котором нет обязательного info или неправильно описаны операции. Поэтому сначала используем специализированный валидатор всего документа OpenAPI 3.1, затем отдельно проверим положительные и отрицательные JSON-примеры по Draft 2020-12.
Для воспроизведения понадобится Python 3.12 и версии PyYAML==6.0.3, jsonschema==4.26.0, openapi-spec-validator==0.9.0. Создайте в каталоге examples файл requirements-dev.txt:
PyYAML==6.0.3
jsonschema==4.26.0
openapi-spec-validator==0.9.0
Установите зависимости в выбранное окружение (лучше отдельное виртуальное) из каталога, содержащего examples/:
python -m pip install -r examples/requirements-dev.txt
Проверьте, что команда python запускает Python 3.12. Затем рядом с openapi.yaml создайте examples/check_openapi.py:
from copy import deepcopy
from pathlib import Path
from jsonschema import Draft202012Validator
from openapi_spec_validator import OpenAPIV31SpecValidator, validate
from yaml import safe_load
def main() -> None:
document = safe_load(Path(__file__).with_name("openapi.yaml").read_text(encoding="utf-8"))
assert isinstance(document, dict)
assert document["openapi"] == "3.1.0"
validate(document, cls=OpenAPIV31SpecValidator)
paths = document["paths"]
assert "get" in paths["/tasks"] and "post" in paths["/tasks"]
assert "get" in paths["/tasks/{taskId}"]
body = paths["/tasks"]["post"]["requestBody"]
assert body["required"] is True
assert body["content"]["application/json"]["schema"]["$ref"] == "#/components/schemas/CreateTask"
param = paths["/tasks/{taskId}"]["get"]["parameters"][0]
assert param["name"] == "taskId" and param["in"] == "path" and param["required"] is True
task = document["components"]["schemas"]["Task"]
Draft202012Validator.check_schema(task)
validator = Draft202012Validator(task)
assert validator.is_valid({"id": 7, "title": "Проверить API", "status": "open"})
assert not validator.is_valid({"id": "7", "title": "Проверить API", "status": "open"})
assert not validator.is_valid({"id": 7, "title": "Проверить API"})
broken = deepcopy(document)
del broken["info"]
assert list(OpenAPIV31SpecValidator(broken).iter_errors())
print("openapi: 3.1.0")
print("oas_document: valid")
print("operations: GET /tasks, POST /tasks, GET /tasks/{taskId}")
print("request_body: required")
print("references: linked")
print("response_schema: valid example accepted")
print("negative_controls: 2 rejected")
print("oas_negative_control: missing info rejected")
if __name__ == "__main__":
main()
Скрипт выполняет три вида проверок. validate(document, cls=OpenAPIV31SpecValidator) проверяет структуру всего OAS-документа, включая обязательные поля и локальные ссылки. Обычные assert проверяют выбранные особенности нашей спецификации: операции, тело создания и обязательный параметр пути. Наконец, Draft202012Validator проверяет схему ответа Task: правильная запись принимается, строковый id и отсутствие status отклоняются.
Отдельный отрицательный контроль удаляет info из копии описания. Программа требует, чтобы валидатор нашёл ошибку. Оригинальный YAML при этом не изменяется. Принцип работы validate и явный выбор OpenAPIV31SpecValidator показаны в документации валидатора.
Запустите проверку:
python -I examples/check_openapi.py
При успешном выполнении скрипт должен вывести ровно следующие строки:
openapi: 3.1.0
oas_document: valid
operations: GET /tasks, POST /tasks, GET /tasks/{taskId}
request_body: required
references: linked
response_schema: valid example accepted
negative_controls: 2 rejected
oas_negative_control: missing info rejected
Это ожидаемый stdout программы, а не результат выполнения HTTP-запросов. Код завершения должен быть 0; исключение означает, что проверка не прошла.
Валидатор принял полное описание Tasks API как OpenAPI 3.1 и отклонил вариант без обязательного поля info.
Валидация спецификации проверяет описание, но не сравнивает его с ответами работающего сервера. Мы не запускали Tasks API. Для проверки реализации потребуются настоящие HTTP-запросы и отдельное сравнение с контрактом.
Как найти ошибку и повторить проверку
Если программа завершилась с исключением, определите, на каком уровне произошёл сбой.
- YAML не разбирается. Проверьте отступы:
paths,componentsиserversдолжны находиться на одном уровне сinfo, аresponses— внутриgetилиpost. Для вложенности используйте пробелы; коды'200','201'и другие оставляйте строками. - OAS-валидатор отвергает документ. Проверьте наличие
openapi,infoиresponsesу каждой операции. Например,responseв единственном числе не заменяет полеresponses. Сверьте названия с нормативным описанием OpenAPI 3.1. - Не разрешается
$ref. Если ссылка написана#/components/schemas/Tasks, а схема называетсяTask, исправьте имя. Обратите внимание на регистр и расположениеcomponents.schemasв том же файле. - Ошибка параметра пути. Для
/tasks/{taskId}нужен параметрname: taskIdсin: pathиrequired: true. Параметр строки запроса с таким именем не заменяет параметр пути. - Не проходит JSON-проверка. Для поля
idчисло7соответствуетtype: integer, а строка"7"— нет. Если отсутствуетstatus, добавьте его в JSON-пример, а не удаляйте изrequired, пока контракт действительно требует это поле. - Не распознаётся
nullкак тип. В записиtype: [string, 'null']кавычки вокруг'null'необходимы: без них YAML-парсер создаёт специальное пустое значение вместо строки с именем типа. JSON Schema отклонит такую схему. Верните кавычки и повторите проверку.
При ModuleNotFoundError проверьте, что зависимости установлены для того же интерпретатора, который запускает проверку: python -m pip show openapi-spec-validator. Если установку выполняли другим Python, повторите её из текущего окружения.
После исправления снова выполните python -I examples/check_openapi.py. Убедитесь, что правильные данные проходят, а намеренно неправильные всё ещё отвергаются: ослабление схемы ради зелёного запуска может скрыть исходную ошибку.
Итог: спецификация и границы проверки
Теперь в одном openapi.yaml описаны три операции Tasks API, необязательный фильтр списка, обязательный идентификатор в пути, тело создания, статусы ответов и три общие JSON-схемы. Этот файл можно согласовать до написания сервера, затем хранить рядом с его реализацией и изменять вместе с интерфейсом.
Проверки имеют разные границы. YAML-парсер отвечает за чтение файла; валидатор OpenAPI — за структуру описания; JSON Schema — за соответствие конкретного объекта объявленной модели. Успех любой из них ещё не означает, что работающий сервер вернёт статус 201, 404 или JSON нужного вида.
Когда реализация Tasks API появится, следующим шагом будут реальные HTTP-запросы: их статусы, заголовки и тела нужно сопоставлять с responses и схемами. Проверка авторизации и предметных правил также потребуется отдельно. До этого YAML остаётся проектируемым контрактом, а не доказательством поведения сервера.
Источники
- OAS 3.1: поля документа, операции, параметры, ответы, ссылки и схемы
- Официальный каталог версий OpenAPI; 3.1 не объявляется последней
- JSON Schema Draft 2020-12: массив типов, null и required
- Python API полного валидатора OpenAPI 3.1
- Демонстрационный адрес сервера внутри учебной спецификации; не источник и не работающий API