Проблема фрагментации ошибок API заключается в отсутствии единого стандарта представления исключений. Разные сервисы возвращают ошибки в произвольном формате, что усложняет обработку на стороне клиента и требует написания специфичных парсеров для каждого провайдера. Стандарт Problem Details (RFC 9457) решает эту задачу, предлагая универсальную структуру JSON-объекта. Внедрение этого формата позволяет унифицировать логику обработки ошибок, упрощает отладку и улучшает пользовательский опыт за счет понятных сообщений.
Введение: проблема фрагментации ошибок API и стандарт Problem Details
Сегодня большинство API возвращают ошибки в виде произвольных JSON-объектов. В одном ответе может быть поле `message`, в другом — `error_description`, а код статуса иногда отсутствует вовсе. Это вынуждает разработчиков писать множество условий для обработки разных форматов, что увеличивает сложность кода и риск ошибок.
Стандарт Problem Details (RFC 9457) предлагает решение: единый формат представления ошибок через JSON-объект с предсказуемой структурой. Он разделяет информацию о типе ошибки, её причине и деталях, что позволяет клиенту корректно реагировать на исключения независимо от источника.
Внедрение этого стандарта не требует изменения бизнес-логики сервера, но меняет способ формирования ответов при возникновении исключений. Это снижает нагрузку на поддержку, упрощает интеграцию новых модулей и делает API более дружелюбным для внешних потребителей.
Шаг 1. Структура объекта `error` согласно спецификации RFC 9457
Объект ошибки должен быть валидным JSON-объектом, который возвращается в теле HTTP-ответа при статусе 4xx или 5xx. Основная структура включает обязательные поля: `type`, `title`, `status` и опциональное поле `detail`. Дополнительно могут присутствовать `instance` и `trace`.
Пример базового объекта ошибки выглядит так:
«`json
{
"type": "https://example.com/errors/account-not-found",
"title": "Account not found",
"status": 404,
"detail": "The requested account does not exist."
}
«`
Поле `type` должно указывать на URI ресурса, который вызвал ошибку. Если такой URI не существует, можно использовать строку вида `about:blank` или ссылку на документацию с описанием ошибки.
Поле `title` содержит краткое, понятное пользователю описание проблемы. Оно должно быть лаконичным и не содержать технических деталей реализации сервера.
Поле `status` указывает HTTP-код статуса, соответствующий уровню серьезности ошибки (4xx для ошибок клиента, 5xx для ошибок сервера).
Поле `detail` предоставляет подробное описание проблемы, которое может быть полезно разработчику или администратору для отладки. Оно должно быть на языке пользователя или разработчика в зависимости от контекста.
Шаг 2. Обязательные поля и их семантика (type, title, status, detail)
Поле `type` указывает на тип ошибки. Оно может быть абсолютным URI или относительным путем. Если используется относительный путь, он должен начинаться с `/`. Например, `/errors/validation-error`.
Поле `title` содержит краткое описание ошибки, понятное пользователю. Оно должно быть написано на языке интерфейса (например, русском), если API ориентировано на конкретного пользователя. Пример: "Неверный формат email".
Поле `status` указывает HTTP-код статуса. Он должен соответствовать уровню серьезности ошибки: 4xx для ошибок клиента (некорректные данные, отсутствие ресурса) и 5xx для ошибок сервера (внутренняя ошибка, перегрузка).
Поле `detail` предоставляет подробное описание проблемы. Оно может содержать рекомендации по исправлению ошибки или ссылки на документацию. Важно не перегружать это поле техническими деталями реализации.
Все обязательные поля должны присутствовать в объекте ошибки. Отсутствие одного из них делает ответ не соответствующим стандарту Problem Details и может привести к проблемам при обработке на стороне клиента.
Шаг 3. Поле `instance`: указание на конкретный ресурс или запрос
Поле `instance` позволяет указать URL ресурса, вызвавшего ошибку. Это упрощает отладку, так как клиент сразу видит, какой именно ресурс был недоступен или некорректно использован.
Пример использования поля `instance`:
«`json
{
"type": "https://example.com/errors/resource-not-found",
"title": "Resource not found",
"status": 404,
"detail": "The requested resource was not found.",
"instance": "/api/v1/users/123"
}
«`
Если ресурс не имеет URL (например, ошибка валидации формы), поле `instance` можно опустить или указать на конкретный элемент формы через идентификатор.
Использование поля `instance` особенно полезно при работе с RESTful API, где каждый ресурс имеет уникальный URI. Это помогает клиенту быстро понять, какой именно запрос вызвал ошибку.
Шаг 4. Поле `trace`: логирование для отладки без утечки чувствительных данных
Поле `trace` используется для передачи идентификатора запроса (request ID) в логировании. Это позволяет связать ошибку с конкретным запросом в логах сервера и упростить поиск проблемы.
Пример использования поля `trace`:
«`json
{
"type": "https://example.com/errors/internal-error",
"title": "Internal server error",
"status": 500,
"detail": "An unexpected error occurred.",
"trace": "req_abc123xyz"
}
«`
Важно не передавать чувствительные данные (пароли, токены) в поле `trace` или любом другом поле объекта ошибки. Используйте только идентификаторы запроса, которые безопасны для передачи.
Поле `trace` должно быть опциональным и использоваться только при необходимости отладки. В продакшене его можно не включать, если есть другие механизмы отслеживания ошибок.
Шаг 5. Кастомные поля: расширение схемы под бизнес-логику
Стандарт Problem Details позволяет добавлять кастомные поля для расширения схемы под бизнес-логику. Это особенно полезно, если нужно передать дополнительную информацию о причине ошибки или рекомендации по исправлению.
Пример добавления кастомного поля:
«`json
{
"type": "https://example.com/errors/validation-error",
"title": "Validation error",
"status": 400,
"detail": "The request body is invalid.",
"errors": [
{"field": "email", "message": "Invalid email format"},
{"field": "age", "message": "Age must be between 18 and 65"}
]
}
«`
Кастомные поля не должны нарушать базовую структуру объекта ошибки. Они могут быть добавлены в любом месте JSON-объекта, но важно соблюдать согласованность формата.
При использовании кастомных полей убедитесь, что они не содержат чувствительных данных и не превышают лимиты размера ответа.
Шаг 6. Коды статусов HTTP и их соответствие уровням ошибок
Коды HTTP статуса должны соответствовать уровню серьезности ошибки. Для ошибок клиента (некорректные данные, отсутствие ресурса) используйте коды 4xx: 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found).
Для ошибок сервера (внутренняя ошибка, перегрузка) используйте коды 5xx: 500 (Internal Server Error), 502 (Bad Gateway), 503 (Service Unavailable).
Пример соответствия кодов статусов:
«`json
{
"type": "https://example.com/errors/unauthorized",
"title": "Unauthorized access",
"status": 401,
"detail": "Authentication credentials were not provided."
}
«`
Не используйте коды 5xx для ошибок клиента и наоборот. Это может привести к неправильной обработке ошибок на стороне клиента.
Шаг 7. Локализация контента: язык, форматирование и кодировка
Локализация контента в ошибках критична для международного использования API. Поля `title` и `detail` должны быть на языке пользователя или разработчика, в зависимости от контекста.
Для поддержки нескольких языков используйте параметр `Accept-Language` в заголовке запроса клиента. Сервер должен возвращать ошибки на соответствующем языке.
Пример локализованной ошибки:
«`json
{
"type": "https://example.com/errors/validation-error",
"title": "Ошибка валидации данных",
"detail": "Поле 'email' должно содержать корректный формат адреса.",
"status": 400,
"locale": "ru-RU"
}
«`
Убедитесь, что кодировка текста в JSON-ответе соответствует UTF-8. Это гарантирует корректное отображение символов на всех поддерживаемых языках.
Шаг 8. Интеграция с клиентом: обработка исключений и отображение
Клиент должен уметь корректно обрабатывать объекты ошибок в формате Problem Details. Это включает парсинг JSON-ответа, проверку кода статуса и отображение соответствующего сообщения пользователю.
Пример обработки ошибки на стороне клиента:
«`javascript
fetch('/api/resource', { method: 'POST' })
.then(response => {
if (!response.ok) {
return response.json().then(error => {
console.error('Ошибка:', error.title, error.detail);
throw new Error(error.detail);
});
}
return response.json();
})
.catch(err => {
// Обработка ошибки
});
«`
При отображении ошибок пользователю используйте только поле `title`. Поле `detail` предназначено для разработчиков и администраторов.
Не показывайте технические детали реализации (например, стек технологий) в сообщениях об ошибках. Это может помочь злоумышленникам найти уязвимости.
Вопросы и ответы
Можно ли использовать стандарт Problem Details для ошибок 2xx?
Нет, стандарт Problem Details предназначен только для ошибок с кодами статуса 4xx и 5xx. Успешные ответы (2xx) не должны содержать объект ошибки.
Что делать, если нужно передать несколько ошибок в одном ответе?
В объекте ошибки можно добавить кастомное поле `errors` с массивом объектов, описывающих каждую ошибку. Пример: `{ "errors": [{"field": "email", "message": "…"}, {"field": "age", "message": "…"}] }`.
Как обеспечить совместимость с другими стандартами ошибок?
Стандарт Problem Details совместим с большинством других форматов ошибок. Если нужно поддерживать несколько стандартов, можно добавить дополнительные поля к объекту ошибки, но не нарушать базовую структуру.
{
"type": "https://example.com/errors/account-not-found",
"title": "Account not found",
"status": 404,
"detail": "The requested account does not exist."
}
{
"type": "https://example.com/errors/resource-not-found",
"title": "Resource not found",
"status": 404,
"detail": "The requested resource was not found.",
"instance": "/api/v1/users/123"
}


