Помилка в коді — це як баг у твоїй улюбленій грі: відчуття неприємне, але іноді це частина процесу. У світі 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);
}
});
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ