この章について
GraphQLサーバーからは、いつもリクエストに応じたデータが得られるとは限りません。
サーバー内でエラーが発生した場合や、そもそもリクエストが不正なものだった場合には、エラーメッセージという形でそれがクライアントに提示されます。
この章では、GraphQLがユーザーに返すエラーデータについて深く掘り下げていきたいと思います。
GraphQLが返すエラー
エラーが持つフィールド
GraphQLクライアントがサーバーから受け取るエラーの形式は、github.com/vektah/gqlparser/v2/gqlerror
パッケージ内のError
構造体として定義されています。
type Error struct {
Message string `json:"message"`
Path ast.Path `json:"path,omitempty"`
Locations []Location `json:"locations,omitempty"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
}
それぞれのフィールドの意味は以下の通りです。
- Message: エラーの内容を表すメッセージフィールド
- Path: GraphQLのクエリの中で、どのフィールドがエラーの原因になったのかを示す
- Locations: GraphQLのクエリの中で、どの行の何文字目がエラーの原因になったのかを示す
- Extensions: Messageの他にもエラーに関するメタ情報を付けたい場合に使う項目
Pathが含まれるエラー例
{
"errors": [
{
"message": "fail to get projectV2 data",
"path": [
"user",
"projectV2"
]
}
],
"data": {
"user": null
}
}
LocationsとExtensionsが含まれるエラー例
{
"errors": [
{
"message": "Expected Name, found <EOF>",
"locations": [
{
"line": 14,
"column": 1
}
],
"extensions": {
"code": "GRAPHQL_PARSE_FAILED"
}
}
],
"data": null
}
リゾルバなどで発生したエラーは、どんなエラーであったとしても最終的にはこのgqlerror.Error
構造体に変換された上でクライアントに渡ります。
複数個のエラーを返すパターン
ここで一つポイントとなるのが、「クライアントから見えるエラーは複数個になることがある」ということです。
例えば、以下のようなクエリを実行したとしましょう。
query {
user(name: "hsaki") {
id
name
projectV2(number: 1) {
title
}
projectV2s(first: 2) {
nodes {
id
}
}
}
}
このクエリを処理している最中に、projectV2
とprojectV2s
の二箇所でエラーが発生したとします。
すると、クライアントが得られるレスポンスは以下のような形になります。
{
"errors": [
{
"message": "projectv2 err",
"path": [
"user",
"projectV2"
]
},
{
"message": "projectv2s err",
"path": [
"user",
"projectV2s"
]
}
],
"data": {
"user": null
}
}
errors
というリストフィールドの中に、2種類のエラー情報が格納されていることがお分かりいただけるのではないでしょうか。
複数個のエラーをユーザーに返す方法
前述の例のように「projectV2
とprojectV2s
という2箇所別々のところ(=分割した別のリゾルバ内)でエラーが起きた」というような場合は、ユーザーに返すエラーも自然と2個になるのですが、1つのリゾルバの中で複数個のエラーを発生させたい場合にはどうすればいいでしょうか。
// 返り値errorに複数個のエラーの情報を詰めたい
func (r *userResolver) ProjectV2(ctx context.Context, obj *model.User, number int) (*model.ProjectV2, error)
実は、その解決方法がGraphQLの公式Docに記載されています。
公式Docのコード例をそのまま引用する形で、やり方を紹介したいと思います。
graphql.AddError
関数を使う
graphql.AddError
関数をリゾルバの中で使うことで、ユーザーに返却するエラーを複数個追加することができます。
// DoThings add errors to the stack.
func (r Query) DoThings(ctx context.Context) (bool, error) {
// Print a formatted string
graphql.AddErrorf(ctx, "Error %d", 1)
// Pass an existing error out
graphql.AddError(ctx, gqlerror.Errorf("zzzzzt"))
// Or fully customize the error
graphql.AddError(ctx, &gqlerror.Error{
Path: graphql.GetPath(ctx),
Message: "A descriptive error message",
Extensions: map[string]interface{}{
"code": "10-4",
},
})
// And you can still return an error if you need
return false, gqlerror.Errorf("BOOM! Headshot")
}
gqlerror.List
構造体を使う
gqlerror.List
は、複数個のエラーをまとめて1つのエラーとして扱うことができるエラー構造体です。
リゾルバの返り値エラーにこのgqlerror.List
構造体を返すことで、クライアントに複数個のエラーを渡すことが可能です。
// DoThingsReturnMultipleErrors collect errors and returns it if any.
func (r Query) DoThingsReturnMultipleErrors(ctx context.Context) (bool, error) {
errList := gqlerror.List{}
// Add existing error
errList = append(errList, gqlerror.Wrap(errSomethingWrong))
// Create new formatted and append
errList = append(errList, gqlerror.Errorf("invalid value: %s", "invalid"))
// Or fully customize the error and append
errList = append(errList, &gqlerror.Error{
Path: graphql.GetPath(ctx),
Message: "A descriptive error message",
Extensions: map[string]interface{}{
"code": "10-4",
},
})
return false, errList
}