# Контракты API

← [Раздел](README.md) · [Главная](../README.md)

## Цель

Научиться проектировать и **документировать контракты API**: стабильность, версии, обратная совместимость, OpenAPI и соглашения об ошибках.

## Предварительно

- [API и шлюзы](../02-komponenty/api-i-shlyuzy.md)
- [Синхронные вызовы](sinhronnye-vyzovy.md)

## Время

**3–5 часов**

---

## Контракт — это обещание

**Контракт API** — соглашение между **производителем** (backend) и **потребителями** (mobile, web, партнёры): форматы, коды, семантика полей. Нарушение контракта = сломанные клиенты в проде без их деплоя.

---

## Единый envelope ответа

Рекомендуемый паттерн единого конверта ответа:

```json
{
  "success": true,
  "data": { "id": "list_1", "title": "Продукты" },
  "error": null,
  "meta": { "page": 1, "limit": 20, "total": 45 }
}
```

| Поле | Назначение |
|------|------------|
| `success` | Быстрая проверка клиентом |
| `data` | Полезная нагрузка или null |
| `error` | Сообщение или объект кода |
| `meta` | Пагинация, request_id |

Ошибка:

```json
{
  "success": false,
  "data": null,
  "error": {
    "code": "LIST_NOT_FOUND",
    "message": "Список не найден"
  },
  "meta": { "request_id": "req_xyz" }
}
```

---

## OpenAPI (Swagger)

Файл `openapi.yaml` описывает paths, schemas, security.

| Выгода | Как используют |
|--------|----------------|
| Документация | Swagger UI |
| Генерация клиентов | openapi-generator |
| Контрактные тесты | Dredd, Schemathesis |
| Mock server | Prism |

**Contract-first:** сначала OpenAPI, потом код — mobile и backend параллельно.

---

## Версионирование

| Изменение | Совместимость |
|-----------|---------------|
| Добавить optional поле | Обратно совместимо |
| Удалить поле | Breaking |
| Сменить тип int→string | Breaking |
| Переименовать поле | Breaking (без alias) |

Правила:

1. Публичный API — `/v1/`, `/v2/`  
2. Deprecation header: `Sunset: Sat, 01 Jan 2028 00:00:00 GMT`  
3. Минимум 6–12 мес на миграцию  

---

## Пагинация

| Стиль | Пример |
|-------|--------|
| Offset | `?page=2&limit=20` — просто, плохо на больших offset |
| Cursor | `?cursor=eyJpZCI6MTB9` — стабильнее при вставках |

В `meta`: `total`, `next_cursor`, `has_more`.

---

## Идентификаторы и время

| Правило | Пример |
|---------|--------|
| ID — строка opaque | `list_2n9k`, не только auto-increment |
| Время — ISO 8601 UTC | `2026-06-16T10:00:00Z` |
| Boolean — true/false | Не 0/1 в JSON |

---

## Ошибки: коды и HTTP

| HTTP | `error.code` | Когда |
|------|--------------|-------|
| 400 | `VALIDATION_ERROR` | Невалидное тело |
| 401 | `UNAUTHORIZED` | Нет/битый токен |
| 403 | `FORBIDDEN` | Нет прав на ресурс |
| 404 | `NOT_FOUND` | Ресурс отсутствует |
| 409 | `CONFLICT` | Дубликат email |
| 429 | `RATE_LIMITED` | Слишком много запросов |
| 500 | `INTERNAL_ERROR` | Без деталей наружу |

Клиенты ветвятся по **`code`**, не по тексту `message` (локализация).

---

## Аутентификация в контракте

```yaml
securitySchemes:
  bearerAuth:
    type: http
    scheme: bearer
    bearerFormat: JWT
```

Документируйте scopes: `lists:read`, `lists:write`.

---

## Consumer-driven contracts (введение)

Mobile команда **требует** поле `owner_name` в GET `/lists`. Pact-тест: consumer пишет ожидание, provider CI проверяет — **не ломаем** незаметно.

---

## gRPC / Protobuf контракт

`.proto` файлы — источник правды; breaking: изменение field number. Для внутренних API — строже, чем REST.

---

## Чеклист перед релизом API

- [ ] OpenAPI обновлён и в git  
- [ ] Нет удаления полей без v2  
- [ ] Примеры request/response  
- [ ] Rate limits описаны  
- [ ] Changelog для партнёров  

---

## Практика

1. Опишите в OpenAPI (YAML фрагмент) `POST /v1/lists` и ответ 201.  
2. Придумайте breaking и non-breaking изменение для `Item` schema.  
3. Задайте envelope для списка с пагинацией cursor.

---

## Самопроверка

1. Почему клиент не должен парсить `message` для логики?  
2. Что такое contract-first?  
3. Зачем заголовок Sunset?

---

## Дальше

→ [05 — Безопасность](../05-bezopasnost/README.md)
