GraphQLエラーハンドリング

# お題
`Type-safe GraphQL for Go`を謳うGolang製GraphQLライブラリである[gqlgen](https://github.com/99designs/gqlgen)を使って、GraphQL Server側のエラーハンドリングについて検討。

# 想定する読者
- Golangについてある程度書ける。
- 「GraphQL is 何？」ではない。
- gqlgenの [getting-started](https://gqlgen.com/getting-started/) で初期セットアップくらいはやったことがある。

# 関連記事索引
- 第12回「[GraphQLにおけるRelayスタイルによるページング実装再考(Window関数使用版)](https://qiita.com/sky0621/items/957c010ba01c7a756ef7)」
- 第11回「[Dataloadersを使ったN+1問題への対応](https://qiita.com/sky0621/items/621f075e4257270a9e02)」
- 第10回「[GraphQL(gqlgen)エラーハンドリング](https://qiita.com/sky0621/items/affc833e770fee8b576e)」
- 第９回「[GraphQLにおける認証認可事例（Auth0 RBAC仕立て）](https://qiita.com/sky0621/items/efbb8fa085094230d0fd)」
- 第８回「[GraphQL/Nuxt.js(TypeScript/Vuetify/Apollo)/Golang(gqlgen)/Google Cloud Storageの組み合わせで動画ファイルアップロード実装例](https://qiita.com/sky0621/items/5c2c565678c102d9d4ed)」
- 第７回「[GraphQLにおけるRelayスタイルによるページング実装（後編：フロントエンド）](https://qiita.com/sky0621/items/fdcdaefeef7cf4b750ac)」
- 第６回「[GraphQLにおけるRelayスタイルによるページング実装（前編：バックエンド）](https://qiita.com/sky0621/items/1e8823200633f2c46013)」
- 第５回「[DB接続付きGraphQLサーバ(by Golang)をローカルマシン上でDockerコンテナ起動](https://qiita.com/sky0621/items/58fa2bcfc0935f6186cb)」
- 第４回「[graphql-codegenでフロントエンドをGraphQLスキーマファースト](https://qiita.com/sky0621/items/f03631f0309f5c375b1d)」
- 第３回「[go+gqlgenでGraphQLサーバを作る（GORM使ってDB接続）](https://qiita.com/sky0621/items/32e3e26189589080e492)」
- 第２回「[NuxtJS(with Apollo)のTypeScript対応](https://qiita.com/sky0621/items/0297f5a026be380a5929)」
- 第１回「[frontendに「nuxtjs/apollo」、backendに「go+gqlgen」の組み合わせでGraphQLサービスを作る](https://qiita.com/sky0621/items/8abd445edba347e8f6f1)」

# 開発環境
## # OS - Linux(Ubuntu)
```
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.5 LTS (Bionic Beaver)"
```

## # バックエンド
### # 言語 - Golang
```
$ go version
go version go1.15.2 linux/amd64
```

### # gqlgen
```
v0.13.0
```

### IDE - Goland
```
GoLand 2020.2.3
Build #GO-202.7319.61, built on September 16, 2020
```

# 今回の全ソース
https://github.com/sky0621/study-gqlgen/tree/v0.2

# 実践
gqlgenを使ってサーバーサイド側でどうGraphQLのエラーハンドリングをするべきか、いくつかの方法を試行してみる。

## １．ベーシックな方法でハンドリング
いくつかのパターンを列挙する。

### server.go
```go
package main

import (
	"log"
	"net/http"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
	"github.com/sky0621/study-gqlgen/errorhandling/graph"
	"github.com/sky0621/study-gqlgen/errorhandling/graph/generated"
)

func main() {
	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)
	log.Fatal(http.ListenAndServe(":8080", nil))
}
```

### schema.graphqls
```graphql
type Query {
  normalReturn: [Todo!]!
  errorReturn: [Todo!]!
  customErrorReturn: [Todo!]!
  customErrorReturn2: [Todo!]!
  customErrorReturn3: [Todo!]!
  customErrorReturn4: [Todo!]!
  panicReturn: [Todo!]!
}

type Todo {
  id: ID!
  text: String!
}
```

### リゾルバー
```go:schema.resolvers.go
package graph

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/99designs/gqlgen/graphql"
	"github.com/sky0621/study-gqlgen/errorhandling/graph/generated"
	"github.com/sky0621/study-gqlgen/errorhandling/graph/model"
	"github.com/vektah/gqlparser/v2/gqlerror"
)

func (r *queryResolver) NormalReturn(ctx context.Context) ([]*model.Todo, error) {
	return []*model.Todo{
		{ID: "001", Text: "something1"},
		{ID: "002", Text: "something2"},
	}, nil
}

func (r *queryResolver) ErrorReturn(ctx context.Context) ([]*model.Todo, error) {
	return nil, errors.New("error occurred")
}

func (r *queryResolver) CustomErrorReturn(ctx context.Context) ([]*model.Todo, error) {
	return nil, gqlerror.Errorf("custom error")
}

func (r *queryResolver) CustomErrorReturn2(ctx context.Context) ([]*model.Todo, error) {
	graphql.AddError(ctx, gqlerror.Errorf("add error"))
	graphql.AddErrorf(ctx, "add error2: %s", time.Now().String())
	return nil, nil
}

func (r *queryResolver) CustomErrorReturn3(ctx context.Context) ([]*model.Todo, error) {
	return nil, &gqlerror.Error{
		Extensions: map[string]interface{}{
			"code":  "A00001",
			"field": "text",
			"value": "トイレ掃除",
		},
	}
}

func (r *queryResolver) CustomErrorReturn4(ctx context.Context) ([]*model.Todo, error) {
	return nil, &gqlerror.Error{
		Extensions: map[string]interface{}{
			"errors": []map[string]interface{}{
				{
					"code":  "A00001",
					"field": "text",
					"value": "トイレ掃除",
				},
				{
					"code":  "A00002",
					"field": "text",
					"value": "トイレ掃除",
				},
			},
		},
	}
}

func (r *queryResolver) PanicReturn(ctx context.Context) ([]*model.Todo, error) {
	panic(fmt.Errorf("panic occurred"))
}

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type queryResolver struct{ *Resolver }
```

### パターン別の解説

#### 参考
GraphQLにおけるレスポンスに関して。
正常系の場合、以下のような構造になる。

```
{
  "data": {
      〜〜〜〜
  }
}
```

リゾルバーで何かしらエラーを返すと、以下のような構造になる。

```
{
  "errors": [
    {
      "message": 〜〜〜〜,
      "path": [〜〜〜〜]
    }
  ],
  "data": null
}
```

その他、以下、参考にされたし。
https://gqlgen.com/reference/errors/

#### ■正常系
```go
func (r *queryResolver) NormalReturn(ctx context.Context) ([]*model.Todo, error) {
	return []*model.Todo{
		{ID: "001", Text: "something1"},
		{ID: "002", Text: "something2"},
	}, nil
}
```

正常系。設定したデータが返される。
![screenshot-localhost_8080-2020.10.17-23_48_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/a15f3cd4-2045-9dae-a749-a7beffd5ba3e.png)

#### ■go標準のエラーを返すパターン
```go
func (r *queryResolver) ErrorReturn(ctx context.Context) ([]*model.Todo, error) {
	return nil, errors.New("error occurred")
}
```

指定したエラーメッセージが`message`に積まれている。
`path`は勝手に付与される。
![screenshot-localhost_8080-2020.10.17-23_51_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/efd37484-45c3-6d71-8b27-04594e5dbbd5.png)

#### ■gqlgenが用意したメソッドを介してエラーを返すパターン
```go
func (r *queryResolver) CustomErrorReturn(ctx context.Context) ([]*model.Todo, error) {
	return nil, gqlerror.Errorf("custom error")
}
```

やはり、指定したエラーメッセージが`message`に積まれている。
構造はgo標準のエラーを返すパターンと同じ。
![screenshot-localhost_8080-2020.10.18-00_08_41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/dac78f5e-f1e5-b9fa-1026-08ada84535f7.png)

#### ■複数のエラーを返すパターン
```go
func (r *queryResolver) CustomErrorReturn2(ctx context.Context) ([]*model.Todo, error) {
	graphql.AddError(ctx, gqlerror.Errorf("add error"))
	graphql.AddErrorf(ctx, "add error2: %s", time.Now().String())
	return nil, nil
}
```

指定した２種類のエラーがそれぞれの`message`に積まれている。
これまでのエラー発生時と違い、`data`が`null`ではなく空スライスが返されているのが、やや気になる。
（おそらくだけど、`return`でerrorを返さなかったためかな。）
![screenshot-localhost_8080-2020.10.18-00_19_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/924a8ad1-da33-cdf6-c4d2-02e7ca9d70ae.png)

#### ■個別拡張領域を使うパターン
```go
func (r *queryResolver) CustomErrorReturn3(ctx context.Context) ([]*model.Todo, error) {
	return nil, &gqlerror.Error{
		Extensions: map[string]interface{}{
			"code":  "A00001",
			"field": "text",
			"value": "トイレ掃除",
		},
	}
}
```

`message`には何も積まず、用意された`extensions`にサービス固有の表現でエラー内容を定義する。
`map[string]interface{}`なので任意の構造が使える。
これにより、レスポンスを受けたフロントエンド側で`code`に応じたエラーメッセージの生成とエンドユーザーへの表示などが可能になる。
![screenshot-localhost_8080-2020.10.18-00_20_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/4aa35dcd-0a5d-4840-1d03-331876873af4.png)

#### ■個別拡張領域を使うパターン2
```go
func (r *queryResolver) CustomErrorReturn4(ctx context.Context) ([]*model.Todo, error) {
	return nil, &gqlerror.Error{
		Extensions: map[string]interface{}{
			"errors": []map[string]interface{}{
				{
					"code":  "A00001",
					"field": "text",
					"value": "トイレ掃除",
				},
				{
					"code":  "A00002",
					"field": "text",
					"value": "トイレ掃除",
				},
			},
		},
	}
}
```

返したいエラーは１つとは限らない。このようにマップのスライスという形で保持すれば複数のエラーを返すことも当然可能。
![screenshot-localhost_8080-2020.10.18-00_24_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/62247ea9-90a3-12bf-b888-35a30dcb01ba.png)

#### ■panicが発生した時のパターン
```go
func (r *queryResolver) PanicReturn(ctx context.Context) ([]*model.Todo, error) {
	panic(fmt.Errorf("panic occurred"))
}
```

`panic`発生時に積まれたメッセージは無視して`internal system error`が`message`には積まれる。
![screenshot-localhost_8080-2020.10.18-00_28_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/db2a2bf0-6f59-e603-a8e9-47d69700d95e.png)

## ２．カスタマイズしたエラーハンドリング
よっぽど小規模なサービスでない限り、サービス固有のエラーハンドリング表現が必要になってくると思う。
gqlgenではhandler生成時に「エラー発生時」と「panic発生時」にフックして処理を追加する仕掛けがある。
この仕掛けを利用して、
リゾルバーからは（エラー発生時）サービス固有に定義したエラー構造体を返し、handlerでフックして、エラー構造体を加工してレスポンスとする実装をしてみる。

### schema.graphqls
```graphql
type Query {
  errorPresenter: [Todo!]!
  panicHandler: [Todo!]!
}

type Todo {
  id: ID!
  text: String!
}
```

### schema.resolvers.go
サービス固有のエラー構造体として`AppError`を作成。リゾルバーからはその構造体を返却。

```go
package graph

import (
	"context"
	"fmt"

	"github.com/sky0621/study-gqlgen/errorhandling2/graph/generated"
	"github.com/sky0621/study-gqlgen/errorhandling2/graph/model"
)

type ErrorCode string

const (
	ErrorCodeRequired            ErrorCode = "1001"
	ErrorCodeUnexpectedSituation ErrorCode = "9999"
)

type AppError struct {
	Code ErrorCode
	Msg  string
}

func (e AppError) Error() string {
	return fmt.Sprintf("[%s]%s", e.Code, e.Msg)
}

func (r *queryResolver) ErrorPresenter(ctx context.Context) ([]*model.Todo, error) {
	return nil, AppError{
		Code: ErrorCodeRequired,
		Msg:  "text is none",
	}
}

func (r *queryResolver) PanicHandler(ctx context.Context) ([]*model.Todo, error) {
	panic("unexpected situation")
}

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type queryResolver struct{ *Resolver }
```

### server.go
`SetErrorPresenter()`でセットする関数の中で、リゾルバーで投げたエラーを受け取り、`AppError`だったら`*gqlerror.Error{}`の構造に編集し直している。
ちなみに、`SetRecoverFunc()`も用意してpanic発生時もサービス固有の想定したエラー表現になるよう編集している。

```go
package main

import (
	"context"
	"errors"
	"log"
	"net/http"

	"github.com/99designs/gqlgen/graphql"
	"github.com/vektah/gqlparser/v2/gqlerror"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
	"github.com/sky0621/study-gqlgen/errorhandling2/graph"
	"github.com/sky0621/study-gqlgen/errorhandling2/graph/generated"
)

func main() {
	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))

	srv.SetErrorPresenter(func(ctx context.Context, e error) *gqlerror.Error {
		err := graphql.DefaultErrorPresenter(ctx, e)

		var appErr graph.AppError
		if errors.As(err, &appErr) {
			return &gqlerror.Error{
				Message: appErr.Msg,
				Extensions: map[string]interface{}{
					"code": appErr.Code,
				},
			}
		}
		return err
	})

	srv.SetRecoverFunc(func(ctx context.Context, err interface{}) error {
		return &gqlerror.Error{
			Extensions: map[string]interface{}{
				"code":  graph.ErrorCodeUnexpectedSituation,
				"cause": err,
			},
		}
	})

	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)

	log.Fatal(http.ListenAndServe(":8080", nil))
}
```

### 動作確認
#### エラーハンドリング
うまいこと、エラーコードとエラーメッセージの振り分けが出来ている。
![screenshot-localhost_8080-2020.10.18-00_43_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/f66a18e5-7ec4-26bd-4c55-6368e3d86e41.png)

#### panicハンドリング
`cause`としてpanic発生時のエラーを積むようにしたため、ちゃんとレスポンスに発生元のエラー内容も乗るようになった。
![screenshot-localhost_8080-2020.10.18-00_45_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/d1f448d2-5f58-0c25-753a-4278346fc170.png)

## ３．汎用性を考慮してカスタマイズしたエラーハンドリング
エラーと一口に言っても、バリデーションエラー、認証系のエラー、DB接続エラー、等々、いろんな種類があり、エラー構造として必要な要素も変わってくると思う。
単一のエラーで返せばいい場合もあれば、（バリデーションエラーのように）エラー要素１つ１つが必要で、結果として複数のエラーを返す必要がある場合もある。
こういった状況を踏まえ、なるべく汎用的にエラーハンドリングすることを試みる。

### サービス固有のエラー構造
```go:apperror.go
package graph

import (
	"context"
	"net/http"

	"github.com/vektah/gqlparser/v2/gqlerror"

	"github.com/99designs/gqlgen/graphql"
)

type AppError struct {
	httpStatusCode int          // http.StatusCodeXXXXXXX を入れる
	appErrorCode   AppErrorCode // サービス固有に定義したエラーコード

	/*
	 * 以下、全てのエラー表現に必須ではない要素（オプションとして設定可能）
	 */
	field string
	value string
}

func (e *AppError) AddGraphQLError(ctx context.Context) {
	extensions := map[string]interface{}{
		"status_code": e.httpStatusCode,
		"error_code":  e.appErrorCode,
	}
	if e.field != "" {
		extensions["field"] = e.field
	}
	if e.value != "" {
		extensions["value"] = e.value
	}
	graphql.AddError(ctx, &gqlerror.Error{
		Message:    "",
		Extensions: extensions,
	})
}

func NewAppError(httpStatusCode int, appErrorCode AppErrorCode, opts ...AppErrorOption) *AppError {
	a := &AppError{
		httpStatusCode: httpStatusCode,
		appErrorCode:   appErrorCode,
	}

	for _, o := range opts {
		o(a)
	}

	return a
}

// 認証エラー用
func NewAuthenticationError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusUnauthorized, AppErrorCodeAuthenticationFailure, opts...)
}

// 認可エラー用
func NewAuthorizationError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusForbidden, AppErrorCodeAuthorizationFailure, opts...)
}

// バリデーションエラー用
func NewValidationError(field, value string, opts ...AppErrorOption) *AppError {
	options := []AppErrorOption{WithField(field), WithValue(value)}
	for _, opt := range opts {
		options = append(options, opt)
	}
	return NewAppError(http.StatusBadRequest, AppErrorCodeValidationFailure, options...)
}

// その他エラー用
func NewInternalServerError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusInternalServerError, AppErrorCodeUnexpectedFailure, opts...)
}

type AppErrorCode string

// MEMO: サービスの定義によっては意味のある文字列よりもコード体系を決めるのもあり。
const (
	// 認証エラー
	AppErrorCodeAuthenticationFailure AppErrorCode = "AUTHENTICATION_FAILURE"
	// 認可エラー
	AppErrorCodeAuthorizationFailure AppErrorCode = "AUTHORIZATION_FAILURE"
	// バリデーションエラー
	AppErrorCodeValidationFailure AppErrorCode = "VALIDATION_FAILURE"

	// その他の予期せぬエラー
	AppErrorCodeUnexpectedFailure AppErrorCode = "UNEXPECTED_FAILURE"
)

type AppErrorOption func(*AppError)

func WithField(v string) AppErrorOption {
	return func(a *AppError) {
		a.field = v
	}
}

func WithValue(v string) AppErrorOption {
	return func(a *AppError) {
		a.value = v
	}
}
```

#### 解説
まず、サービス固有のエラー構造体として`AppError`を作成。
エラー要素として何を持つかはサービスによりまちまちだとは思うけど、とりあえず以下２つはエラーの内容問わず必須として定義。

- HTTPステータスコード
- サービス固有のエラーコード

```go
type AppError struct {
	httpStatusCode int          // http.StatusCodeXXXXXXX を入れる
	appErrorCode   AppErrorCode // サービス固有に定義したエラーコード
　　　　　〜〜
}

func NewAppError(httpStatusCode int, appErrorCode AppErrorCode, opts ...AppErrorOption) *AppError {
	a := &AppError{
		httpStatusCode: httpStatusCode,
		appErrorCode:   appErrorCode,
	}
　　　　　〜〜
}
```

続いて、例えばバリデーションエラーのように「どのフィールドのどの値が」という情報も欲しくなるようなケースのために構造体内には（冗長でも）パターン別に必要な要素を持たせるようにする。

```go
type AppError struct {
　　　　　〜〜
	/*
	 * 以下、全てのエラー表現に必須ではない要素（オプションとして設定可能）
	 */
	field string
	value string
}
```

ただし、今後、こういった要素の追加が必要になるたびに`New`関数を修正（つまり、呼び元も全て修正）なんてしたくないので、[Functional Option Pattern](https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html)を用いることにする。

オプション適用のための関数を定義し、`New`関数では可変引数で渡す（つまり、ないならないでOK）ようにする。

```go
type AppErrorOption func(*AppError)

func NewAppError(httpStatusCode int, appErrorCode AppErrorCode, opts ...AppErrorOption) *AppError {
	a := &AppError{
		httpStatusCode: httpStatusCode,
		appErrorCode:   appErrorCode,
	}

	for _, o := range opts {
		o(a)
	}

	return a
}
```

で、`AppErrorOption`の適用事例として以下２つを用意。

```go
func WithField(v string) AppErrorOption {
	return func(a *AppError) {
		a.field = v
	}
}

func WithValue(v string) AppErrorOption {
	return func(a *AppError) {
		a.value = v
	}
}
```

こうすることで、今後、エラー構造体に追加したい要素が増えても、既存の呼び出し元を修正する必要なく拡張できる。

（説明が雑なのが一番の理由だけど）初見でこの仕組みを理解するのは、けっこうキツいと思うので、「Functional Option Pattern」でググってもらって易しい解説記事を読んでもらいたい。。。

あとは、サービス固有のエラーコードを以下のように定義して、

```go
type AppErrorCode string

// MEMO: サービスの定義によっては意味のある文字列よりもコード体系を決めるのもあり。
const (
	// 認証エラー
	AppErrorCodeAuthenticationFailure AppErrorCode = "AUTHENTICATION_FAILURE"
	// 認可エラー
	AppErrorCodeAuthorizationFailure AppErrorCode = "AUTHORIZATION_FAILURE"
	// バリデーションエラー
	AppErrorCodeValidationFailure AppErrorCode = "VALIDATION_FAILURE"

	// その他の予期せぬエラー
	AppErrorCodeUnexpectedFailure AppErrorCode = "UNEXPECTED_FAILURE"
)
```

エラーのタイプ別に専用の`New`関数でも用意してあげればOK。

```go

// 認証エラー用
func NewAuthenticationError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusUnauthorized, AppErrorCodeAuthenticationFailure, opts...)
}

// 認可エラー用
func NewAuthorizationError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusForbidden, AppErrorCodeAuthorizationFailure, opts...)
}

// バリデーションエラー用
func NewValidationError(field, value string, opts ...AppErrorOption) *AppError {
	options := []AppErrorOption{WithField(field), WithValue(value)}
	for _, opt := range opts {
		options = append(options, opt)
	}
	return NewAppError(http.StatusBadRequest, AppErrorCodeValidationFailure, options...)
}

// その他エラー用
func NewInternalServerError(opts ...AppErrorOption) *AppError {
	return NewAppError(http.StatusInternalServerError, AppErrorCodeUnexpectedFailure, opts...)
}
```

### リゾルバー
試しに、タイプ別にエラーを生成してGraphQLエラーとして追加してあげるとこんな感じ。
（認証エラーなんかは当然、ユーザーIDなどを積んだりするだろうけど、とりあえずはサンプルなので。）

```go:schema.resolvers.go
package graph

import (
	"context"

	"github.com/sky0621/study-gqlgen/errorhandling3/graph/generated"
	"github.com/sky0621/study-gqlgen/errorhandling3/graph/model"
)

func (r *queryResolver) CustomErrorReturn(ctx context.Context) ([]*model.Todo, error) {
	// 認証エラーを追加
	NewAuthenticationError().AddGraphQLError(ctx)

	// 認可エラーを追加
	NewAuthorizationError().AddGraphQLError(ctx)

	// バリデーションエラーを追加
	NewValidationError("name", "taro").AddGraphQLError(ctx)

	// その他のエラーを追加
	NewInternalServerError().AddGraphQLError(ctx)

	return nil, nil
}

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type queryResolver struct{ *Resolver }
```

### schema.graphqls
```graphql
type Query {
  customErrorReturn: [Todo!]!
}

type Todo {
  id: ID!
  text: String!
}
```

### server.go
今回はhandlerへの仕込みは無し。

```go
package main

import (
	"log"
	"net/http"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
	"github.com/sky0621/study-gqlgen/errorhandling3/graph"
	"github.com/sky0621/study-gqlgen/errorhandling3/graph/generated"
)

func main() {
	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)
	log.Fatal(http.ListenAndServe(":8080", nil))
}
```

### 動作確認
この通り、統一されたフォーマットになっているので、レスポンスを受け取る側でのハンドリングもしやすいはず。。。
![screenshot-localhost_8080-2020.10.18-01_22_59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/1ea3da43-8e31-5ed1-9e02-b94488fb2256.png)

# まとめ
とりあえずエラーが返せればいいといったシンプルなものから、一応、汎用性を考慮してサービス固有のエラー構造を定義したやり方まで複数のエラーハンドリング案を提示してみた。
もちろん、ここにあるパターン以外にもあるだろうし、ここにあげたものはプロダクションレベルとしては心もとない。
１サービスとして考えるなら、ここで返したエラー内容をフロントエンドではどのようにハンドリングするかも重要な要素だと思う。