Ошибка в коде — это как баг в вашей любимой игре: ощущение неприятное, но иногда это часть процесса. В мире GraphQL важно не просто генерировать ошибки, а делать это так, чтобы это не вызывало ужас у разработчиков фронтенда и не утягивало производительность вашего бэкенда. Разработка надежного API включает продуманную обработку ошибок, чтобы разработчики могли понимать, что пошло не так, а пользователи не сталкивались с белым экраном смерти.
Как работает обработка ошибок в GraphQL?
Одной из интересных особенностей GraphQL является то, что он способен вернуть клиенту данные и ошибки в одном ответе. Формат ответа выглядит примерно так:
{
"data": {
"user": {
"id": "1",
"name": "Иван"
}
},
"errors": [
{
"message": "Что-то пошло не так",
"locations": [{ "line": 2, "column": 3 }],
"path": ["user", "age"]
}
]
}
Обратите внимание, что данные, которые можно вернуть, всё равно отправляются клиенту, даже если часть запроса не удалась. Это важно для работы сложных запросов, где клиенту, возможно, нужны только те данные, которые он всё-таки смог получить.
Типы ошибок в GraphQL
Ошибки в GraphQL можно разделить на следующие категории:
- Ошибки схемы: если запрос содержит недопустимые поля, типы или аргументы.
- Ошибки валидации: когда данные, которые клиент пытается отправить, не соответствуют ожидаемым требованиям.
- Ошибки авторизации/аутентификации: когда пользователь не имеет права на выполнение запроса.
- Системные ошибки: ошибки на стороне сервера, например, проблемы с базой данных или нарушением логики.
Каждый тип ошибок обрабатывается по-разному, и мы разберем их шаг за шагом.
Основы обработки ошибок в GraphQL с Django
Django работает с GraphQL через библиотеку Graphene. Она предоставляет мощные инструменты для обработки ошибок.
Пример базового обработчика ошибок
Когда что-то идёт не так, можно использовать GraphQLError для генерации ошибок:
from graphql import GraphQLError
class Query(graphene.ObjectType):
user = graphene.Field(UserType, id=graphene.ID(required=True))
def resolve_user(self, info, id):
if not id.isdigit():
raise GraphQLError("ID должен быть числом.")
try:
user = User.objects.get(id=id)
return user
except User.DoesNotExist:
raise GraphQLError("Пользователь с таким ID не найден.")
Что здесь происходит?
- Если
idне является числом, мы сразу "швыряем" понятную ошибку. - Если пользователь не найден, мы снова возвращаем ошибку, но уже с другим сообщением.
На фронтенде это проявится в виде ошибки в массиве errors, что позволяет разработчикам фронта адекватно обработать проблему.
Кастомизация ошибок
Иногда недостаточно просто выкинуть GraphQLError. Вам может понадобиться добавить к сообщению ошибки дополнительную информацию.
Пример с кастомной ошибкой
from graphql import GraphQLError
class PermissionDeniedError(GraphQLError):
def __init__(self, message="Недостаточно прав для выполнения действия"):
super().__init__(message, extensions={"code": "PERMISSION_DENIED"})
Теперь мы можем использовать её в любом месте:
def resolve_sensitive_data(self, info):
user = info.context.user
if not user.is_authenticated:
raise PermissionDeniedError()
# Возвращаем данные, если всё нормально
return {"secret": "42"}
На клиенте extensions добавляются к ошибке JSON. Это полезно для фронтенда, чтобы отображать ошибки в удобном для пользователя виде.
Обработка ошибок на уровне глобального сервера
Если вы хотите отлавливать ошибки на глобальном уровне (например, для логирования или единого формата ошибок), можно переопределить обработчик ошибок в Graphene:
from graphql.execution.middleware import MiddlewareManager
class CustomMiddleware:
def on_error(self, error):
# Логируем ошибку
print(f"Ошибка: {error}")
# Можно добавить здесь кастомное поведение
schema = graphene.Schema(query=Query, middleware=[CustomMiddleware()])
Теперь любые ошибки, которые происходят в вашем приложении, попадут в этот обработчик.
Пример объединения данных и ошибок
Иногда вам нужно вернуть часть данных, даже если часть запроса вызывает ошибку. Например:
class Query(graphene.ObjectType):
user = graphene.Field(UserType, id=graphene.ID(required=True))
posts = graphene.List(PostType)
def resolve_user(self, info, id):
try:
return User.objects.get(id=id)
except User.DoesNotExist:
raise GraphQLError("Пользователь не найден.")
def resolve_posts(self, info):
return Post.objects.all()
Если пользователя не существует, клиент всё равно получит список постов, но вместе с ним придёт ошибка в поле errors.
Советы по обработке ошибок
- Делайте сообщения понятными: не пишите что-то вроде "Ручка сломалась". Укажите, что пошло не так и как это исправить.
- Не возвращайте лишнюю информацию: не говорите злоумышленникам, где находится брешь в защите.
- Используйте коды ошибок: это помогает фронтенд-разработчикам проще отлаживать приложение.
- Логируйте ошибки: всегда записывайте ошибки сервера, чтобы потом на их основе улучшать приложение.
Обработка ошибок на фронтенде: краткое замечание
Если вы работаете с GraphQL на фронтенде (например, через Apollo Client), вы можете обрабатывать массив errors прямо в клиенте. Пример:
client.query({
query: GET_USER,
variables: { id: 123 }
})
.then(response => {
if (response.errors) {
console.error("Ошибка:", response.errors[0].message);
} else {
console.log("Данные пользователя:", response.data.user);
}
});
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ