1 package codegen
2
3 import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "sort"
8 "strings"
9
10 "github.com/vektah/gqlparser/v2/ast"
11
12 "github.com/99designs/gqlgen/codegen/config"
13 )
14
15
16
17 type Data struct {
18 Config *config.Config
19 Schema *ast.Schema
20
21
22
23
24
25
26 AllDirectives DirectiveList
27 Objects Objects
28 Inputs Objects
29 Interfaces map[string]*Interface
30 ReferencedTypes map[string]*config.TypeReference
31 ComplexityRoots map[string]*Object
32
33 QueryRoot *Object
34 MutationRoot *Object
35 SubscriptionRoot *Object
36 AugmentedSources []AugmentedSource
37 Plugins []interface{}
38 }
39
40 func (d *Data) HasEmbeddableSources() bool {
41 hasEmbeddableSources := false
42 for _, s := range d.AugmentedSources {
43 if s.Embeddable {
44 hasEmbeddableSources = true
45 }
46 }
47 return hasEmbeddableSources
48 }
49
50
51 type AugmentedSource struct {
52
53 RelativePath string
54 Embeddable bool
55 BuiltIn bool
56 Source string
57 }
58
59 type builder struct {
60 Config *config.Config
61 Schema *ast.Schema
62 Binder *config.Binder
63 Directives map[string]*Directive
64 }
65
66
67 func (d *Data) Directives() DirectiveList {
68 res := DirectiveList{}
69 for k, directive := range d.AllDirectives {
70 for _, s := range d.Config.Sources {
71 if directive.Position.Src.Name == s.Name {
72 res[k] = directive
73 break
74 }
75 }
76 }
77 return res
78 }
79
80 func BuildData(cfg *config.Config, plugins ...interface{}) (*Data, error) {
81
82 cfg.ReloadAllPackages()
83
84 b := builder{
85 Config: cfg,
86 Schema: cfg.Schema,
87 }
88
89 b.Binder = b.Config.NewBinder()
90
91 var err error
92 b.Directives, err = b.buildDirectives()
93 if err != nil {
94 return nil, err
95 }
96
97 dataDirectives := make(map[string]*Directive)
98 for name, d := range b.Directives {
99 if !d.Builtin {
100 dataDirectives[name] = d
101 }
102 }
103
104 s := Data{
105 Config: cfg,
106 AllDirectives: dataDirectives,
107 Schema: b.Schema,
108 Interfaces: map[string]*Interface{},
109 Plugins: plugins,
110 }
111
112 for _, schemaType := range b.Schema.Types {
113 switch schemaType.Kind {
114 case ast.Object:
115 obj, err := b.buildObject(schemaType)
116 if err != nil {
117 return nil, fmt.Errorf("unable to build object definition: %w", err)
118 }
119
120 s.Objects = append(s.Objects, obj)
121 case ast.InputObject:
122 input, err := b.buildObject(schemaType)
123 if err != nil {
124 return nil, fmt.Errorf("unable to build input definition: %w", err)
125 }
126
127 s.Inputs = append(s.Inputs, input)
128
129 case ast.Union, ast.Interface:
130 s.Interfaces[schemaType.Name], err = b.buildInterface(schemaType)
131 if err != nil {
132 return nil, fmt.Errorf("unable to bind to interface: %w", err)
133 }
134 }
135 }
136
137 if s.Schema.Query != nil {
138 s.QueryRoot = s.Objects.ByName(s.Schema.Query.Name)
139 } else {
140 return nil, fmt.Errorf("query entry point missing")
141 }
142
143 if s.Schema.Mutation != nil {
144 s.MutationRoot = s.Objects.ByName(s.Schema.Mutation.Name)
145 }
146
147 if s.Schema.Subscription != nil {
148 s.SubscriptionRoot = s.Objects.ByName(s.Schema.Subscription.Name)
149 }
150
151 if err := b.injectIntrospectionRoots(&s); err != nil {
152 return nil, err
153 }
154
155 s.ReferencedTypes = b.buildTypes()
156
157 sort.Slice(s.Objects, func(i, j int) bool {
158 return s.Objects[i].Definition.Name < s.Objects[j].Definition.Name
159 })
160
161 sort.Slice(s.Inputs, func(i, j int) bool {
162 return s.Inputs[i].Definition.Name < s.Inputs[j].Definition.Name
163 })
164
165 if b.Binder.SawInvalid {
166
167 err := cfg.Packages.Errors()
168 if len(err) > 0 {
169 return nil, err
170 }
171
172
173 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")
174 }
175 aSources := []AugmentedSource{}
176 for _, s := range cfg.Sources {
177 wd, err := os.Getwd()
178 if err != nil {
179 return nil, fmt.Errorf("failed to get working directory: %w", err)
180 }
181 outputDir := cfg.Exec.Dir()
182 sourcePath := filepath.Join(wd, s.Name)
183 relative, err := filepath.Rel(outputDir, sourcePath)
184 if err != nil {
185 return nil, fmt.Errorf("failed to compute path of %s relative to %s: %w", sourcePath, outputDir, err)
186 }
187 relative = filepath.ToSlash(relative)
188 embeddable := true
189 if strings.HasPrefix(relative, "..") || s.BuiltIn {
190 embeddable = false
191 }
192 aSources = append(aSources, AugmentedSource{
193 RelativePath: relative,
194 Embeddable: embeddable,
195 BuiltIn: s.BuiltIn,
196 Source: s.Input,
197 })
198 }
199 s.AugmentedSources = aSources
200
201 return &s, nil
202 }
203
204 func (b *builder) injectIntrospectionRoots(s *Data) error {
205 obj := s.Objects.ByName(b.Schema.Query.Name)
206 if obj == nil {
207 return fmt.Errorf("root query type must be defined")
208 }
209
210 __type, err := b.buildField(obj, &ast.FieldDefinition{
211 Name: "__type",
212 Type: ast.NamedType("__Type", nil),
213 Arguments: []*ast.ArgumentDefinition{
214 {
215 Name: "name",
216 Type: ast.NonNullNamedType("String", nil),
217 },
218 },
219 })
220 if err != nil {
221 return err
222 }
223
224 __schema, err := b.buildField(obj, &ast.FieldDefinition{
225 Name: "__schema",
226 Type: ast.NamedType("__Schema", nil),
227 })
228 if err != nil {
229 return err
230 }
231
232 obj.Fields = append(obj.Fields, __type, __schema)
233
234 return nil
235 }
236
View as plain text