errors
с типизированными пользовательскими ошибками.DEPRECATED! В этом правиле предлагается использовать Union-типы, но практика показала, что их тяжело создавать бэкендерам, да и фронтендерам не особо удобно ими пользоваться. Вместо этого, предлагается использовать интерфейсы.
В резолвере можно выбросить эксепшн, и тогда ошибка улетает на глобальный уровень, но так делать нельзя по следующим причинам:
type Mutation {
likePost(id: 1): LikePostPayload
}
type LikePostPayload {
record: Post
+ errors: [LikePostProblems!]
}
Мутации должны возвращать пользовательские ошибки или ошибки бизнес-логики сразу в Payload’е мутации в поле errors
. Все ошибки необходимо описать с суффиксом Problem
. И для самой мутации завести Union-тип ошибок, где будут перечислены возможные пользовательские ошибки. Это позволит легко определять ошибки на клиентской стороне, сразу понимать что может пойти не так. И более того, позволит клиенту дозапросить дополнительные метаданные по ошибке.
Для начала, необходимо создать интерфейс для ошибок и можно объявить пару глобальных ошибок. Интерфейс необходим, чтобы можно было считать текстовое сообщение в не зависимости от того, какая ошибка вернулась. А вот каждую конкретную ошибку уже можно расширить дополнительными значениями, например в ошибке SpikeProtectionProblem
добавлено поле wait
:
interface ProblemInterface {
message: String!
}
type AccessRightProblem implements ProblemInterface {
message: String!
}
type SpikeProtectionProblem implements ProblemInterface {
message: String!
# Timout in seconds when the next operation will be executed without errors
wait: Int!
}
type PostDoesNotExistsProblem implements ProblemInterface {
message: String!
postId: Int!
}
Ну а дальше можно описать нашу мутацию likePost
с возвратом пользовательских ошибок:
type Mutation {
likePost(id: Int!): LikePostPayload
}
union LikePostProblems = SpikeProtectionProblem | PostDoesNotExistsProblem;
type LikePostPayload {
recordId: Int
# `record` is nullable! If there is an error we may return null for Post
record: Post
errors: [LikePostProblems!]
}
Благодаря union-типу LikePostProblems
теперь через интроспекцию фронтендеры знаю какие ошибки могут вернутся при вызове мутации likePost
. К примеру, для такого запроса они для любого типа ошибки смогут считать название ошибки с поля __typename
, а вот благодаря интерфейсу считать message
из любого типа ошибки:
mutation {
likePost(id: 666) {
errors {
__typename
... on ProblemInterface {
message
}
}
}
}
А если клиенты умные, то можно запросить дополнительные поля по необходимым по ошибкам:
mutation {
likePost(id: 666) {
recordId
record {
title
likes
}
errors {
__typename
... on ProblemInterface {
message
}
... on SpikeProtectionProblem {
message
wait
}
... on PostDoesNotExistsProblem {
message
postId
}
}
}
}
И получить ответ от сервера в таком виде:
{
data: {
likePost: {
errors: [
{
__typename: 'PostDoesNotExistsProblem',
message: 'Post does not exists!',
postId: 666,
},
{
__typename: 'SpikeProtectionProblem',
message: 'Spike protection! Please retry later!',
wait: 20,
},
],
record: { likes: 0, title: 'Post 666' },
recordId: 666,
},
},
}