[GraphQL]gqlgenを使ってアレがしたい

## 概要
gqlgenを使ってGraphQLサーバを実装している。
今は開発に慣れてきた部分があるが、gqlgenの導入時に調査が必要であったアレやるには？コレやるには？を少し書いてみる。

しかし、どれも大体公式ドキュメントに乗っている内容なので詳細はそちらを見るのがよい

## 公式doc
- [GraphQL](http://spec.graphql.org/)
- [gqlgen](https://gqlgen.com/)

## エラーをフックしてアプリ固有の処理をしたい
例えばResolverからエラーを返す際に必ず以下のことを行いたいケースがあるとする

- Internalエラーはログ出力したい
- エラーに応じて決められたjsonフォーマットでエラーメッセージを返したい

このようなニーズに応えるためにgqlgenのhandler.Serverには`SetErrorPresenter`という関数でresolverが返したエラーをHookする仕組みがある。

GraphQLでのエラーメッセージは`extensions`というjsonキーにセットする。
他のjson階層には追加できない (将来的にGraphQL側の名前と被る可能性があるため)
https://github.com/graphql/graphql-spec/releases/tag/June2018

```main.go
func main() {
	// 一部抜粋
	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{}))
	srv.SetErrorPresenter(func(ctx context.Context, err error) *gqlerror.Error {
		return &gqlerror.Error{
			Message: err.Error(),
			Extensions: map[string]interface{}{
				// 実際には引数のerrorをerrors.As()なりで、動的にtype, codeなどをセットする
				"type": "Internal",
				"code": "1002567",
			},
		}
	})
}
```

## panicを制御したい
panic発生時にアプリケーションのプロセスが終了しないように制御したいことはただある。
エラーのHookと同じくgqlgenのhandler.Serverには`SetRecoverFunc`関数が用意されていて、これを使用してpanicした際にアプリケーションのプロセスを落とさないように制御することができる

```main.go
func main() {
	// 一部抜粋
	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{}))
	srv.SetRecoverFunc(func(ctx context.Context, err interface{}) error {
		log.From(ctx).Error("recovered", zap.Error(fmt.Errorf("%v", err)))

		return &gqlerror.Error{
			Message: "server error",
			Extensions: map[string]interface{}{
				"type": "Internal",
				"code": "Unknown",
			},
		}
	})
}
```

## カスタム型を作りたい
gqlgenには予め[用意された型](https://gqlgen.com/reference/scalars/)が用意されているがアプリケーション独自に定義した型を使用したい場合がある。
その型を指定した場合、どのような振る舞いを行うかを表現するためのインターフェースがある。

それは`UnmarshalGQL`と`MarshalGQL`である。
`UnmarshalGQL`はリクエストされた値をstructにバインドするときに呼び出される関数で、`MarshalGQL`はjsonレスポンスを返す手前の処理として呼び出される関数。

例えば`UUID`という独自の型を定義し利用する場合は以下のようになる

```schema.graphql
scalar UUID
```

```gqlgen.yaml
# 一部抜粋
models:
  UUID:
    model: github.com/graphql-app/graph/model.UUID
```

```uuid.go
package model

type UUID struct {
	string
}

// UnmarshalGQL implements the graphql.Unmarshaler interface
func (u *UUID) UnmarshalGQL(v interface{}) error {
	str, ok := v.(string)
	if !ok {
		return fmt.Errorf("uuid must be string")
	}

	if _, err := uuid.Parse(str); err != nil {
		return fmt.Errorf("not in uuid format: %w", err)
	}

	u.string = str

	return nil
}

// MarshalGQL implements the graphql.Marshaler interface
func (u UUID) MarshalGQL(w io.Writer) {
	_, _ = io.WriteString(w, strconv.Quote(u.string))
}
```

## .graphqlスキーマファイルを分割したい
gqlgenの[example](https://github.com/99designs/gqlgen/tree/master/example)ではschema.graphqlの中にQuery, Mutation, scalarなど宣言されているケースが多い。1つのファイルに追記する形でももちろん良いが、役割ごとにファイル分割したい場合は以下のように分割ができる。

※`gqlgen.yml`で設定した拡張子にだけ揃えればファイル名は任意で問題ない

```schema.graphql
schema {
    query: Query
    mutation: Mutation
}

type Query
type Mutation
```

```user.graphql
type User {
    id: ID!
    name: String!
}

# QueryやMutationはextendキーワードを使用して宣言する
extend type Query {
    getUser: User!
}

extend type Mutation {
    addUser: User!
}
```

```scalar.graphql
scalar UUID
```

## 気軽にQueryを叩いたり、GraphQLスキーマを見たりしたい
grpcのリフレクションと似たような機能で、`Introspection`を有効にすると定義しているスキーマ一覧を参照できる。
gqlgenには`playground.Handler`というWebUIを表示する機能が内包されているので、これと組み合わせるとスキーマ定義からQueryやMutionを入力補完を効かせてシュッと確認することができて最高である。

ただし、この機能は開発時のみONにするのがよい。

PlayGroundと組み合わせて使用する場合は次のような感じ

```main.go
func main() {
	// 一部抜粋
	srv := handler.New(generated.NewExecutableSchema(generated.Config{}))
	srv.AddTransport(transport.Options{})
	srv.AddTransport(transport.GET{})
	srv.AddTransport(transport.POST{})

	// 環境変数なりでON/OFFするとよい
	usePlayGround := true
	if usePlayGround {
		srv.Use(extension.Introspection{})
	}
	// ここではルーティングに"github.com/go-chi/chi"を使う前提。ルーティングは好きなものを使ってOK
	router := chi.NewRouter()
	router.Group(func(r chi.Router) {
		r.Handle("/graphql", srv)
		if usePlayGround {
			r.Handle("/", playground.Handler("graphql playground", "/graphql"))
		}
	})
}
```

### PlayGroundのWebUI
![gqlgen_playground.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72518/6acd3cca-7695-c2ab-e6c1-f2dfc18aefd4.png)


## 他にも
いろいろな機能がサポートされているが、使ったことがない機能が沢山あるので利用しだい追記する
(Dataloaders、Directives、Cachingとかとか使ってみたい)