forked from Yamashou/gqlgenc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsource_generator.go
231 lines (194 loc) · 6.75 KB
/
source_generator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
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
}