stash/vendor/github.com/Yamashou/gqlgenc/clientgen/source_generator.go

232 lines
6.8 KiB
Go

package clientgen
import (
"fmt"
"go/types"
"strings"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/vektah/gqlparser/v2/ast"
)
type Argument struct {
Variable string
Type types.Type
}
type ResponseField struct {
Name string
IsFragmentSpread bool
IsInlineFragment bool
Type types.Type
Tags []string
ResponseFields ResponseFieldList
}
type ResponseFieldList []*ResponseField
func (rs ResponseFieldList) StructType() *types.Struct {
vars := make([]*types.Var, 0)
structTags := make([]string, 0)
for _, filed := range rs {
// クエリーのフィールドの子階層がFragmentの場合、このフィールドにそのFragmentの型を追加する
if filed.IsFragmentSpread {
typ, ok := filed.ResponseFields.StructType().Underlying().(*types.Struct)
if !ok {
continue
}
for j := 0; j < typ.NumFields(); j++ {
vars = append(vars, typ.Field(j))
structTags = append(structTags, typ.Tag(j))
}
} else {
vars = append(vars, types.NewVar(0, nil, templates.ToGo(filed.Name), filed.Type))
structTags = append(structTags, strings.Join(filed.Tags, " "))
}
}
return types.NewStruct(vars, structTags)
}
func (rs ResponseFieldList) IsFragment() bool {
if len(rs) != 1 {
return false
}
return rs[0].IsInlineFragment || rs[0].IsFragmentSpread
}
func (rs ResponseFieldList) IsBasicType() bool {
return len(rs) == 0
}
func (rs ResponseFieldList) IsStructType() bool {
return len(rs) > 0 && !rs.IsFragment()
}
type SourceGenerator struct {
cfg *config.Config
binder *config.Binder
client config.PackageConfig
}
func NewSourceGenerator(cfg *config.Config, client config.PackageConfig) *SourceGenerator {
return &SourceGenerator{
cfg: cfg,
binder: cfg.NewBinder(),
client: client,
}
}
func (r *SourceGenerator) NewResponseFields(selectionSet ast.SelectionSet) ResponseFieldList {
responseFields := make(ResponseFieldList, 0, len(selectionSet))
for _, selection := range selectionSet {
responseFields = append(responseFields, r.NewResponseField(selection))
}
return responseFields
}
func (r *SourceGenerator) NewResponseFieldsByDefinition(definition *ast.Definition) (ResponseFieldList, error) {
fields := make(ResponseFieldList, 0, len(definition.Fields))
for _, field := range definition.Fields {
if field.Type.Name() == "__Schema" || field.Type.Name() == "__Type" {
continue
}
var typ types.Type
if field.Type.Name() == "Query" || field.Type.Name() == "Mutation" {
var baseType types.Type
baseType, err := r.binder.FindType(r.client.Pkg().Path(), field.Type.Name())
if err != nil {
if !strings.Contains(err.Error(), "unable to find type") {
return nil, fmt.Errorf("not found type: %w", err)
}
// create new type
baseType = types.NewPointer(types.NewNamed(
types.NewTypeName(0, r.client.Pkg(), templates.ToGo(field.Type.Name()), nil),
nil,
nil,
))
}
// for recursive struct field in go
typ = types.NewPointer(baseType)
} else {
baseType, err := r.binder.FindTypeFromName(r.cfg.Models[field.Type.Name()].Model[0])
if err != nil {
return nil, fmt.Errorf("not found type: %w", err)
}
typ = r.binder.CopyModifiersFromAst(field.Type, baseType)
}
tags := []string{
fmt.Sprintf(`json:"%s"`, field.Name),
fmt.Sprintf(`graphql:"%s"`, field.Name),
}
fields = append(fields, &ResponseField{
Name: field.Name,
Type: typ,
Tags: tags,
})
}
return fields, nil
}
func (r *SourceGenerator) NewResponseField(selection ast.Selection) *ResponseField {
switch selection := selection.(type) {
case *ast.Field:
fieldsResponseFields := r.NewResponseFields(selection.SelectionSet)
var baseType types.Type
switch {
case fieldsResponseFields.IsBasicType():
baseType = r.Type(selection.Definition.Type.Name())
case fieldsResponseFields.IsFragment():
// 子フィールドがFragmentの場合はこのFragmentがフィールドの型になる
// if a child field is fragment, this field type became fragment.
baseType = fieldsResponseFields[0].Type
case fieldsResponseFields.IsStructType():
baseType = fieldsResponseFields.StructType()
default:
// ここにきたらバグ
// here is bug
panic("not match type")
}
// GraphQLの定義がオプショナルのはtypeのポインタ型が返り、配列の定義場合はポインタのスライスの型になって返ってきます
// return pointer type then optional type or slice pointer then slice type of definition in GraphQL.
typ := r.binder.CopyModifiersFromAst(selection.Definition.Type, baseType)
tags := []string{
fmt.Sprintf(`json:"%s"`, selection.Alias),
fmt.Sprintf(`graphql:"%s"`, selection.Alias),
}
return &ResponseField{
Name: selection.Alias,
Type: typ,
Tags: tags,
ResponseFields: fieldsResponseFields,
}
case *ast.FragmentSpread:
// この構造体はテンプレート側で使われることはなく、ast.FieldでFragment判定するために使用する
fieldsResponseFields := r.NewResponseFields(selection.Definition.SelectionSet)
typ := types.NewNamed(
types.NewTypeName(0, r.client.Pkg(), templates.ToGo(selection.Name), nil),
fieldsResponseFields.StructType(),
nil,
)
return &ResponseField{
Name: selection.Name,
Type: typ,
IsFragmentSpread: true,
ResponseFields: fieldsResponseFields,
}
case *ast.InlineFragment:
// InlineFragmentは子要素をそのままstructとしてもつので、ここで、構造体の型を作成します
fieldsResponseFields := r.NewResponseFields(selection.SelectionSet)
return &ResponseField{
Name: selection.TypeCondition,
Type: fieldsResponseFields.StructType(),
IsInlineFragment: true,
Tags: []string{fmt.Sprintf(`graphql:"... on %s"`, selection.TypeCondition)},
ResponseFields: fieldsResponseFields,
}
}
panic("unexpected selection type")
}
func (r *SourceGenerator) OperationArguments(variableDefinitions ast.VariableDefinitionList) []*Argument {
argumentTypes := make([]*Argument, 0, len(variableDefinitions))
for _, v := range variableDefinitions {
argumentTypes = append(argumentTypes, &Argument{
Variable: v.Variable,
Type: r.binder.CopyModifiersFromAst(v.Type, r.Type(v.Type.Name())),
})
}
return argumentTypes
}
// Typeの引数に渡すtypeNameは解析した結果からselectionなどから求めた型の名前を渡さなければいけない
func (r *SourceGenerator) Type(typeName string) types.Type {
goType, err := r.binder.FindTypeFromName(r.cfg.Models[typeName].Model[0])
if err != nil {
// 実装として正しいtypeNameを渡していれば必ず見つかるはずなのでpanic
panic(fmt.Sprintf("%+v", err))
}
return goType
}