13. Формат ошибок — RFC 9457 Problem Details
Для кодов ответа НЕ 2xx используется единая структура по RFC 9457 Problem Details. Допустимо добавлять кастомные атрибуты под нужды проекта.
13.1 Обязательно
-
R-ERR-1. Тело ошибки соответствует структуре RFC 9457:
{ "type": "urn:problem:order-service:validation-error", "status": 400, "title": "Validation Error", "detail": "Поле amount должно быть больше 0.", "instance": "urn:uuid:9f2d6c22-8e6d-4c2a-9b41-6b9a5e2f6c10", "traceId": "00-1f2a8b6c7d3e4f5a9b0c1d2e3f4a5b6c-7a8b9c0d1e2f3a4b-01", "code": "VALIDATION_ERROR" }Атрибуты:
type— стабильный URI/URN, идентифицирующий категорию ошибки. Подробнее вR-ERR-2.status— HTTP-статус ответа.title— короткое описание (обычно совпадает с названием статуса).instance— уникальный идентификатор инцидента (URI/URN).traceId— ID трассировки (из заголовкаtraceparent, см.R-HDR-4в Заголовки).code— символьный enum-код для программной логики. Все возможные коды ошибок указаны как enum в контракте.detail— человекочитаемое сообщение для отображения пользователю (может быть на русском). Допустимо динамическое формирование по маске; допустимо несколько вариантовdetailдля одногоcode.
-
R-ERR-2.
type— стабильный идентификатор категории ошибки. Одна и та же категория всегда возвращает один и тот жеtype. Это не ссылка на Swagger/OpenAPI, не JSON-схема, не динамический URL.Допустимые формы:
-
URL на резолвимую страницу документации:
"type": "https://errors.example.com/order/not-found" "type": "https://developer.example.com/errors/insufficient-stock"Страница описывает: что за ошибка, почему возникает, как исправить. Может располагаться на developer-портале, wiki, внутреннем портале документации.
-
URN формы
urn:problem:<service>:<code>— используется, если портала документации ошибок нет:"type": "urn:problem:order-service:order-not-found" "type": "urn:problem:catalog:product-archived"URN сохраняет машиночитаемую категорию ошибки и не требует deployment портала.
Отношение к другим полям:
type— категория ошибки, стабильная (стандарт RFC).code— enum для программной логики на клиенте (ORDER_NOT_FOUND). Удобная работа в коде без парсинга URI.instance— уникальный URN конкретного инцидента, меняется при каждом вызове.detail— текст для отображения пользователю.
-
-
R-ERR-3. Content-Type ответа с ошибкой —
application/problem+json. -
R-ERR-4.
codeформируется как UPPER_SNAKE_CASE. Все возможные коды перечислены в OpenAPI как enum.INTERNAL_SERVER_ERROR MISSING_DEFAULT_CARD APPLICATION_ALREADY_SENT ORDER_EMPTY EXT_SYSTEM_UNAVAILABLE -
R-ERR-5. Ошибки валидации полей возвращаются с HTTP-кодом
400 Bad Request,code = VALIDATION_ERRORи расширениемviolations— массивом ошибок по конкретным полям:{ "type": "urn:problem:order-service:validation-error", "status": 400, "title": "Bad Request", "detail": "Ошибка валидации входных данных", "instance": "urn:uuid:9f2d6c22-8e6d-4c2a-9b41-6b9a5e2f6c10", "traceId": "00-1f2a8b6c7d3e4f5a9b0c1d2e3f4a5b6c-7a8b9c0d1e2f3a4b-01", "code": "VALIDATION_ERROR", "violations": [ { "field": "amount", "message": "Сумма должна быть больше 0" }, { "field": "deliveryAddress.zipCode", "message": "Почтовый индекс обязателен" }, { "field": "items[0].quantity", "message": "Количество должно быть от 1 до 99" } ] } -
R-ERR-6. Правила формирования
violations:field— путь к полю в теле запроса. Dot-notation для вложенных объектов (deliveryAddress.zipCode), индексы для массивов (items[0].quantity).message— человекочитаемое описание ошибки для отображения рядом с полем.- Массив содержит все ошибки валидации, а не только первую — клиент должен подсветить все невалидные поля за один запрос.
- Если ошибка относится к объекту целиком (не к конкретному полю) —
fieldне указывается или равен пустой строке.
-
R-ERR-7. OpenAPI-схема
ProblemDetailsиViolation. ENUM-списокErrorCodeформируется один на контракт.ErrorCode: type: string description: 'Символьный код ошибки' enum: - INTERNAL_SERVER_ERROR ProblemDetails: type: object description: 'Problem Details (RFC 9457)' properties: type: type: string format: uri description: 'URI или URN, идентифицирующий тип ошибки' status: type: integer format: int32 description: 'HTTP статус' title: type: string description: 'Краткое описание ошибки' detail: type: string description: 'Подробности ошибки' instance: type: string format: uri description: 'URI конкретного экземпляра ошибки' traceId: type: string code: $ref: '#/components/schemas/ErrorCode' violations: type: array description: 'Ошибки валидации по полям. Присутствует только при code=VALIDATION_ERROR' items: $ref: '#/components/schemas/Violation' Violation: type: object required: - message properties: field: type: string description: 'Путь к полю (dot-notation). Отсутствует если ошибка относится к объекту целиком' example: 'deliveryAddress.zipCode' message: type: string description: 'Описание ошибки для отображения пользователю' example: 'Почтовый индекс обязателен' -
R-ERR-8. Все доступные ошибки указаны как
examplesв объектах ответа каждого метода (готовые YAML — в нижеследующей секции 13.3). -
R-ERR-9. Используемые HTTP-коды ошибок:
400и500— всегда401и403— если есть аутентификация и авторизация404— если обращение к конкретному объекту по ID409— конфликт при конкурентном обновлении (оптимистичная блокировка, дубликат)410— для удалённых deprecated-эндпоинтов (см.R-DEP-3в Rate limiting, файлы, deprecation)429— при превышении лимита запросов (см.R-RATE-1в Rate limiting, файлы, deprecation)
13.2 Запрещено
-
R-ERR-X1. Content-Type
application/jsonдля тела ошибки. Используйapplication/problem+json. -
R-ERR-X2.
type: "about:blank". Теряется машиночитаемая категория ошибки. Если портала документации нет — используй URN формыurn:problem:<service>:<code>(см.R-ERR-2). -
R-ERR-X3. HTTP-коды ошибок вне списка
R-ERR-9(например,418,422,451). Если возникает потребность — обсуждай в архитектурном комитете. -
R-ERR-X4. Stack traces, SQL-запросы, внутренние пути в теле ошибки
500.
13.3 Examples в OpenAPI (для копирования в контракт)
400 Bad Request — всегда
"400":
description: 'Bad Request'
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
validation:
summary: 'Ошибка валидации полей'
value:
type: "urn:problem:order-service:validation-error"
status: 400
title: "Bad Request"
detail: "Ошибка валидации входных данных"
code: VALIDATION_ERROR
violations:
- field: "amount"
message: "Сумма должна быть больше 0"
- field: "deliveryAddress.zipCode"
message: "Почтовый индекс обязателен"
malformed:
summary: 'Некорректный формат запроса'
value:
type: "urn:problem:order-service:malformed-request"
status: 400
title: "Bad Request"
detail: "Невозможно разобрать тело запроса"
code: MALFORMED_REQUEST
401 Unauthorized — если есть аутентификация
"401":
description: 'Unauthorized'
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
expired:
summary: 'Токен истёк'
value:
type: "urn:problem:order-service:token-expired"
status: 401
title: "Unauthorized"
detail: "Токен доступа истёк. Получите новый токен."
code: TOKEN_EXPIRED
missing:
summary: 'Токен отсутствует'
value:
type: "urn:problem:order-service:token-missing"
status: 401
title: "Unauthorized"
detail: "Заголовок Authorization отсутствует"
code: TOKEN_MISSING
403 Forbidden — если есть авторизация
"403":
description: 'Forbidden'
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
forbidden:
summary: 'Нет прав'
value:
type: "urn:problem:order-service:access-denied"
status: 403
title: "Forbidden"
detail: "Недостаточно прав для выполнения операции"
code: ACCESS_DENIED
404 Not Found — если обращение к конкретному объекту по ID
Также возвращается, если ресурс существует, но не принадлежит текущему пользователю (чтобы не подтверждать существование чужих ресурсов).
"404":
description: 'Not Found'
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
notfound:
summary: 'Объект не найден'
value:
type: "urn:problem:order-service:order-not-found"
status: 404
title: "Not Found"
detail: "Заказ не найден"
code: ORDER_NOT_FOUND
409 Conflict — при конкурентном обновлении или дубликате
"409":
description: 'Conflict'
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
concurrent:
summary: 'Конкурентное обновление'
value:
type: "urn:problem:order-service:concurrent-modification"
status: 409
title: "Conflict"
detail: "Ресурс был изменён другим запросом. Повторите операцию с актуальной версией."
code: CONCURRENT_MODIFICATION
duplicate:
summary: 'Дубликат ресурса'
value:
type: "urn:problem:order-service:duplicate-order"
status: 409
title: "Conflict"
detail: "Заказ с таким номером уже существует"
code: DUPLICATE_ORDER
410 Gone — для удалённых deprecated-эндпоинтов
Эндпоинт удалён после прохождения даты Sunset (см. R-DEP-* в Rate limiting, файлы, deprecation).
"410":
description: 'Gone'
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
removed:
summary: 'Эндпоинт удалён'
value:
type: "urn:problem:order-service:endpoint-removed"
status: 410
title: "Gone"
detail: "Эндпоинт удалён. Используйте GET /api/v2/orders/{id}."
code: ENDPOINT_REMOVED
429 Too Many Requests — если есть rate limiting
"429":
description: 'Too Many Requests'
headers:
Retry-After:
schema:
type: integer
description: 'Секунд до сброса лимита'
RateLimit-Limit:
schema:
type: integer
description: 'Максимальное количество запросов в окне'
RateLimit-Remaining:
schema:
type: integer
description: 'Оставшееся количество запросов'
RateLimit-Reset:
schema:
type: integer
description: 'Unix timestamp сброса окна'
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
ratelimit:
summary: 'Превышен лимит запросов'
value:
type: "urn:problem:order-service:rate-limit-exceeded"
status: 429
title: "Too Many Requests"
detail: "Превышен лимит запросов. Повторите через 30 секунд."
code: RATE_LIMIT_EXCEEDED
500 Internal Server Error — всегда
"500":
description: 'Internal Server Error'
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
internal:
summary: 'Внутренняя ошибка'
value:
type: "urn:problem:order-service:internal"
status: 500
title: "Internal Server Error"
detail: "Внутренняя ошибка, попробуйте позже."
code: INTERNAL_SERVER_ERROR
extsystem:
summary: 'Внешняя система недоступна'
value:
type: "urn:problem:order-service:ext-system-unavailable"
status: 500
title: "Internal Server Error"
detail: "Внешняя система недоступна, попробуйте позже."
code: EXT_SYSTEM_UNAVAILABLE
Все существующие бизнес-ошибки должны быть отражены в examples контракта. Указывать пример всех атрибутов, формируемых аналитиками: code, detail.