Коды ошибок (V2 и ACP)
В V2 и ACP все ошибки возвращаются в формате:
{
"time": 1700000000,
"error": {
"code": "string",
"message": "string",
"details": { "key": "value" }
}
}- code: машинночитаемый код.
- message: краткое описание.
- details: детали (валидатор, поля и т.п.); присутствует всегда, но может быть
null.
HTTP-статус передаётся только в статус-строке ответа и в теле не дублируется.
Поле message не привязано к таблицам ниже: если доменная ошибка несёт собственное описание, оно подставляется в message поверх дефолтного текста бакета (not_found, invalid, conflict, unsupported, internal), поэтому клиент и логи видят конкретную причину, а не общий текст. Неклассифицированная ошибка тоже отдаётся со статусом 500 и кодом system.internal, но message несёт её реальный текст, а не заглушку «Internal server error». Ошибки разбора и валидации тела запроса приводятся к validation.invalid_input (422).
Источники:
- тип ошибки API и конструкторы:
interfaces/api/apierrors/app_error.go— тонкий ре-экспорт общей библиотекиshared/apierrors - маппинг доменных ошибок в ответ и выбор HTTP-статуса:
shared/apierrors(Map,StatusForCode) - рендер тела ответа: middleware
interfaces/api/middlewares/error_handler.go(делегирует вshared/apierrors) - доменные коды:
domain/errors/errors.go— ре-экспортshared/domainerr; MARV-специфичные расширения (version vector) вdomain/errors/version_conflict.go
Стандартные ошибки API (apierrors)
| code | http_status | message |
|---|---|---|
| auth.unauthorized | 401 | Unauthorized |
| auth.invalid_headers | 400 | Invalid headers |
| auth.invalid_token | 401 | Invalid token |
| auth.token_expired | 401 | Token expired |
| auth.session_replaced | 401 | Session replaced by a newer login |
| auth.client_version_too_low | 400 | Client version too low |
| auth.banned | 403 | User is banned |
| auth.forbidden | 403 | Forbidden |
| validation.invalid_input | 422 | Invalid input |
| validation.conflict | 409 | Conflict |
| system.internal | 500 | Internal server error |
| system.try_again_later | 503 | Please try again later |
| db.select_failed | 500 | Database select failed |
| db.update_failed | 500 | Database update failed |
Дополнительно при маппинге доменных ошибок:
| code | http_status | message | источник |
|---|---|---|---|
| resource.not_found | 404 | Not found | mapper |
| system.unsupported | 501 | Not implemented | mapper |
Ошибки привязки идентичностей (identity)
Возвращаются эндпоинтами портируемых аккаунтов (/v2/identities/connect, /disconnect, /resolve-conflict). Определены в interfaces/api/controllers/v2/identities.go.
| code | http_status | message |
|---|---|---|
| identity.conflict | 409 | Login belongs to another account |
| identity.last_identity | 422 | Cannot remove the last identity |
| identity.last_strong | 422 | Cannot remove the last strong login while a guest login remains |
| identity.not_owned | 404 | Identity not linked to this account |
| identity.unauthorized_link | 422 | Cannot link a guest login to a secured account |
Доменные коды (domain/errors)
Эти коды маппятся в AppError через mapper.go:
| domain code | Назначение | Маппинг в API |
|---|---|---|
| unauthorized | нет авторизации | auth.unauthorized (401) |
| forbidden | запрет доступа | auth.forbidden (403) |
| not_found | ресурс не найден | resource.not_found (404) |
| invalid | неверные данные | validation.invalid_input (422) |
| conflict | конфликт состояния | validation.conflict (409) |
| unsupported | не поддерживается | system.unsupported (501) |
| internal | внутренняя ошибка | system.internal (500) |
Примечание: точный HTTP‑статус выбирается маппером, исходя из доменного кода.
Доменная ошибка может нести стабильный machine-код (slug) в поле code ответа — тогда клиент ветвится по нему, а message показывает человеку. HTTP‑статус всё равно берётся из бакета (conflict→409, invalid→422, forbidden→403 и т.д.).
Клановые коды (clans)
Специфичные code для клан-эндпоинтов (/v2/clans/*), чтобы клиент понимал причину:
| code | http_status | причина |
|---|---|---|
| clan.tag_taken | 409 | Тег клана уже занят (глобально уникален) |
| clan.already_in_clan | 409 | Игрок уже состоит в клане |
| clan.duplicate_request | 409 | Активная заявка на вступление уже существует |
| clan.full | 422 | Клан заполнен (member_cap) |
| clan.not_open | 422 | Клан не open — прямой вход недоступен |
| clan.open_join_directly | 422 | Клан open — вступать нужно напрямую (не заявкой) |
| clan.invite_only | 422 | Клан только по приглашению |
| clan.request_limit | 422 | Превышен лимит активных заявок игрока |
| clan.not_in_clan | 422 | Игрок не состоит в клане |
| clan.leader_must_transfer | 422 | Лидер должен передать лидерство перед выходом |
| clan.name_tag_required | 422 | Имя и тег клана обязательны |
| clan.badwords | 422 | Имя/тег содержат запрещённые слова |
| clan.conflict | 409 | Прочий конфликт уникальности на клан-эндпоинте |
Коды clan.tag_taken / clan.already_in_clan / clan.duplicate_request возвращаются в т.ч. на гонках (перевод Postgres unique_violation в 409, а не 500).
Коды клановых войн (clan_war)
Специфичные code для эндпоинтов войны (/v2/clans/war/*). Все — bucket invalid → HTTP 422; клиент ветвится по code, а не по общему validation.invalid_input.
| code | HTTP статус | Описание |
|---|---|---|
| clan_war.no_active_war | 422 | Активной войны нет |
| clan_war.no_active_phase | 422 | Нет активной фазы войны |
| clan_war.no_battle_phase | 422 | Текущая фаза — не боевая (battle) |
| clan_war.no_squad_phase | 422 | Отрядная фаза не активна |
| clan_war.not_points_phase | 422 | Текущая фаза — не очковая (points) |
| clan_war.battle_phase_over | 422 | Боевая фаза уже завершена |
| clan_war.not_in_battle_roster | 422 | Игрок не в боевом ростере фазы |
| clan_war.no_attacks | 422 | У игрока не осталось атак в фазе |
| clan_war.invalid_target | 422 | Некорректная цель атаки |
| clan_war.target_beaten | 422 | Цель уже побеждена в этой фазе |
| clan_war.battle_in_progress | 422 | Цель уже под атакой (эксклюзивный лок) — атака не тратится |
| clan_war.invalid_points | 422 | Некорректное значение очков (должно быть > 0) |
Ошибки version vector (vclock)
Version vector используется для оптимистичного контроля конкурентности при обновлении data пользователя. Ошибки возвращаются в стандартном формате AppError c HTTP 409/422 и details содержащим обе версии:
{
"time": 1700000000,
"error": {
"code": "version.dominated",
"message": "Version dominated by server",
"details": {
"server_version_vector": { "server": 2, "xxx-device": 3000 },
"client_version_vector": { "xxx-device": 2999 }
}
}
}| code | http_status | описание |
|---|---|---|
| version.dominated | 409 | Сервер имеет более новую версию (incoming ≤ server) |
| version.concurrent | 409 | Конфликт: часть полей новее, часть старше |
| version.server_key_update | 409 | Клиент пытается инкрементить ключ «server» |
| version.unparsable | 422 | Невалидный формат version_vector |
V1 маппинг: в V1 API те же ошибки возвращаются через числовые статус-коды:
| V1 status | code | описание |
|---|---|---|
| 3001 | ErrorVersionDominated | Сервер доминирует |
| 3002 | ErrorVersionConcurrent | Конкурентный конфликт |
| 3003 | ErrorVersionInvalid | Невалидный вектор |
| 3004 | ErrorVersionServerKeyUpdate | Попытка инкремента ключа «server» |
| 3005 | ErrorVersionUnparsable | Невозможно распарсить |
