🥺
gqlgenで生成される構造体にカスタムディレクティブで指定した任意のタグを設定する
こんなスキーマファイルから
directive @tag(
validate: String
) on INPUT_FIELD_DEFINITION
input NewUser {
email: String! @tag(validate: "required,email")
password: String! @tag(validate: "required,max=50")
name: String! @tag(validate: "required,max=50")
}
こんなモデルを作りたい。
type NewUser struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,max=50"`
Name string `json:"name" validate:"required,max=50"`
}
modelgen.PluginのMutateHookを使用してタグを設定するレシピが載っているのでこれをベースにカスタマイズしていく。
元のコードではモデルのフィールド情報しか参照出来ないので、スキーマファイルで定義した情報を取得してくる必要がある
fd *ast.FieldDefinition = cfg.Schema.Types[model.Name].Fields[i]
これが取得できれば、フィールド定義からディレクティブを取得してレシピと同じように field.Tag
に追記すればいい。
//go:build ignore
package main
import (
"fmt"
"os"
"github.com/99designs/gqlgen/api"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin"
"github.com/99designs/gqlgen/plugin/modelgen"
"github.com/vektah/gqlparser/v2/ast"
)
func fieldHook(f *modelgen.Field, fd *ast.FieldDefinition) {
// @tagディレクティブ
directive := fd.Directives.ForName("tag")
if directive != nil {
// validateタグを追加
validateTag := directive.Arguments.ForName("validate")
if validateTag != nil {
f.Tag += fmt.Sprintf(` validate:"%s"`, validateTag.Value.Raw)
}
}
}
func main() {
cfg, err := config.LoadConfigFromDefaultLocations()
if err != nil {
fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
os.Exit(2)
}
// Attaching the mutation function onto modelgen plugin
p := modelgen.Plugin{
MutateHook: func(b *modelgen.ModelBuild) *modelgen.ModelBuild {
for _, model := range b.Models {
for i, field := range model.Fields {
fieldHook(field, cfg.Schema.Types[model.Name].Fields[i])
}
}
return b
},
}
// ディレクティブとしては使用しないのでコードに出力されないように設定
// https://github.com/99designs/gqlgen/blob/v0.14.0/codegen/config/config.go#L231
cfg.Directives["tag"] = config.DirectiveConfig{
SkipRuntime: true,
}
err = api.Generate(cfg,
func(cfg *config.Config, plugins *[]plugin.Plugin) {
for i, plugin := range *plugins {
if _, ok := plugin.(*modelgen.Plugin); ok {
// modelgen.Pluginを置き換える
(*plugins)[i] = &p
}
}
},
)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(3)
}
}
これを適当な場所において実行すればタグが設定されたモデルが生成される。
go run cmd/gqlgen/gen.go
ポイントとしては、そのままだとディレクティブとしてのコードが生成されてしまうので、 @goField
と同じようにコードが生成されないようにする必要がある。
cfg.Directives["tag"] = config.DirectiveConfig{
SkipRuntime: true,
}
また、元のコードのまま
err = api.Generate(cfg,
api.NoPlugins(),
api.AddPlugin(&p),
)
とすると、モデルしか生成されなくなってしまうので、うまく差し替える方法がないか探してみたが見つけられなかったので型変換できたものを置き換えるようにしてみた。
この例では validate
にしか対応していないが、以下の部分と同じようにやれば任意のフォーマットでやりたいように出来る。
// validateタグを追加
validateTag := directive.Arguments.ForName("validate")
if validateTag != nil {
f.Tag += fmt.Sprintf(` validate:"%s"`, validateTag.Value.Raw)
}
Discussion