forked from 99designs/gqlgen
-
Notifications
You must be signed in to change notification settings - Fork 0
/
data.go
235 lines (201 loc) · 5.9 KB
/
data.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
232
233
234
235
package codegen
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"github.com/vektah/gqlparser/v2/ast"
"github.com/trevor-scheer/gqlgen/codegen/config"
)
// Data is a unified model of the code to be generated. Plugins may modify this structure to do things like implement
// resolvers or directives automatically (eg grpc, validation)
type Data struct {
Config *config.Config
Schema *ast.Schema
// If a schema is broken up into multiple Data instance, each representing part of the schema,
// AllDirectives should contain the directives for the entire schema. Directives() can
// then be used to get the directives that were defined in this Data instance's sources.
// If a single Data instance is used for the entire schema, AllDirectives and Directives()
// will be identical.
// AllDirectives should rarely be used directly.
AllDirectives DirectiveList
Objects Objects
Inputs Objects
Interfaces map[string]*Interface
ReferencedTypes map[string]*config.TypeReference
ComplexityRoots map[string]*Object
QueryRoot *Object
MutationRoot *Object
SubscriptionRoot *Object
AugmentedSources []AugmentedSource
Plugins []interface{}
}
func (d *Data) HasEmbeddableSources() bool {
hasEmbeddableSources := false
for _, s := range d.AugmentedSources {
if s.Embeddable {
hasEmbeddableSources = true
}
}
return hasEmbeddableSources
}
// AugmentedSource contains extra information about graphql schema files which is not known directly from the Config.Sources data
type AugmentedSource struct {
// path relative to Config.Exec.Filename
RelativePath string
Embeddable bool
BuiltIn bool
Source string
}
type builder struct {
Config *config.Config
Schema *ast.Schema
Binder *config.Binder
Directives map[string]*Directive
}
// Get only the directives which are defined in the config's sources.
func (d *Data) Directives() DirectiveList {
res := DirectiveList{}
for k, directive := range d.AllDirectives {
for _, s := range d.Config.Sources {
if directive.Position.Src.Name == s.Name {
res[k] = directive
break
}
}
}
return res
}
func BuildData(cfg *config.Config, plugins ...interface{}) (*Data, error) {
// We reload all packages to allow packages to be compared correctly.
cfg.ReloadAllPackages()
b := builder{
Config: cfg,
Schema: cfg.Schema,
}
b.Binder = b.Config.NewBinder()
var err error
b.Directives, err = b.buildDirectives()
if err != nil {
return nil, err
}
dataDirectives := make(map[string]*Directive)
for name, d := range b.Directives {
if !d.Builtin {
dataDirectives[name] = d
}
}
s := Data{
Config: cfg,
AllDirectives: dataDirectives,
Schema: b.Schema,
Interfaces: map[string]*Interface{},
Plugins: plugins,
}
for _, schemaType := range b.Schema.Types {
switch schemaType.Kind {
case ast.Object:
obj, err := b.buildObject(schemaType)
if err != nil {
return nil, fmt.Errorf("unable to build object definition: %w", err)
}
s.Objects = append(s.Objects, obj)
case ast.InputObject:
input, err := b.buildObject(schemaType)
if err != nil {
return nil, fmt.Errorf("unable to build input definition: %w", err)
}
s.Inputs = append(s.Inputs, input)
case ast.Union, ast.Interface:
s.Interfaces[schemaType.Name], err = b.buildInterface(schemaType)
if err != nil {
return nil, fmt.Errorf("unable to bind to interface: %w", err)
}
}
}
if s.Schema.Query != nil {
s.QueryRoot = s.Objects.ByName(s.Schema.Query.Name)
} else {
return nil, fmt.Errorf("query entry point missing")
}
if s.Schema.Mutation != nil {
s.MutationRoot = s.Objects.ByName(s.Schema.Mutation.Name)
}
if s.Schema.Subscription != nil {
s.SubscriptionRoot = s.Objects.ByName(s.Schema.Subscription.Name)
}
if err := b.injectIntrospectionRoots(&s); err != nil {
return nil, err
}
s.ReferencedTypes = b.buildTypes()
sort.Slice(s.Objects, func(i, j int) bool {
return s.Objects[i].Definition.Name < s.Objects[j].Definition.Name
})
sort.Slice(s.Inputs, func(i, j int) bool {
return s.Inputs[i].Definition.Name < s.Inputs[j].Definition.Name
})
if b.Binder.SawInvalid {
// if we have a syntax error, show it
err := cfg.Packages.Errors()
if len(err) > 0 {
return nil, err
}
// otherwise show a generic error message
return nil, fmt.Errorf("invalid types were encountered while traversing the go source code, this probably means the invalid code generated isnt correct. add try adding -v to debug")
}
aSources := []AugmentedSource{}
for _, s := range cfg.Sources {
wd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("failed to get working directory: %w", err)
}
outputDir := cfg.Exec.Dir()
sourcePath := filepath.Join(wd, s.Name)
relative, err := filepath.Rel(outputDir, sourcePath)
if err != nil {
return nil, fmt.Errorf("failed to compute path of %s relative to %s: %w", sourcePath, outputDir, err)
}
relative = filepath.ToSlash(relative)
embeddable := true
if strings.HasPrefix(relative, "..") || s.BuiltIn {
embeddable = false
}
aSources = append(aSources, AugmentedSource{
RelativePath: relative,
Embeddable: embeddable,
BuiltIn: s.BuiltIn,
Source: s.Input,
})
}
s.AugmentedSources = aSources
return &s, nil
}
func (b *builder) injectIntrospectionRoots(s *Data) error {
obj := s.Objects.ByName(b.Schema.Query.Name)
if obj == nil {
return fmt.Errorf("root query type must be defined")
}
__type, err := b.buildField(obj, &ast.FieldDefinition{
Name: "__type",
Type: ast.NamedType("__Type", nil),
Arguments: []*ast.ArgumentDefinition{
{
Name: "name",
Type: ast.NonNullNamedType("String", nil),
},
},
})
if err != nil {
return err
}
__schema, err := b.buildField(obj, &ast.FieldDefinition{
Name: "__schema",
Type: ast.NamedType("__Schema", nil),
})
if err != nil {
return err
}
obj.Fields = append(obj.Fields, __type, __schema)
return nil
}