In the resolver, you can throw the exhibition, and then the error flies to the global level, but you can not do so for the following reasons:
type Mutation {
likePost(id: 1): LikePostPayload
}
type LikePostPayload {
record: Post
+ errors: [LikePostProblems!]
}
Mutations should return user errors and business logic error immediately in the Payload of the mutation into the field errors
. All errors must be described with the suffix Problem. And for the mutation itself, you need to have a Union-type of errors, where possible user errors will be listed. This will make it easy to identify errors on the client side and immediately understand what can go wrong. Moreover, it will allow the client to request additional metadata about the error.
First, you need to create an interface for errors and you declare a couple of global errors. The interface is required to be able to read a text message no matter what error is returned. But each specific error can already be extended with additional values, for example, in the error SpikeProtectionProblem the wait field is added:
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 PostDoesNotExistProblem implements ProblemInterface {
message: String!
postId: Int!
}
Well, then you can describe our mutation likePost with the return of user errors:
type Mutation {
likePost(id: Int!): LikePostPayload
}
union LikePostProblems = SpikeProtectionProblem | PostDoesNotExistProblem;
type LikePostPayload {
recordId: Int
# `record` is nullable! If there is an error we may return null for Post
record: Post
errors: [LikePostProblems!]
}
Thanks to the union-type LikePostProblems now through introspection clients will know what errors could be returned when you call the mutation likePost. For example, for such a request, they can read the name of the error from the __typename field for any type of error. Also thanks to the interface they can also read the message from any type of errors:
mutation {
likePost(id: 666) {
errors {
__typename
... on ProblemInterface {
message
}
}
}
}
And if clients are smart, you can request additional fields for the necessary errors:
mutation {
likePost(id: 666) {
recordId
record {
title
likes
}
errors {
__typename
... on ProblemInterface {
message
}
... on SpikeProtectionProblem {
message
wait
}
... on PostDoesNotExistProblem {
message
postId
}
}
}
}
And get a response from the server in this shape:
{
data: {
likePost: {
errors: [
{
__typename: 'PostDoesNotExistProblem',
message: 'Post does not exist!',
postId: 666,
},
{
__typename: 'SpikeProtectionProblem',
message: 'Spike protection! Please retry later!',
wait: 20,
},
],
record: { likes: 0, title: 'Post 666' },
recordId: 666,
},
},
}