1 package codegen
2
3 import (
4 "embed"
5 "errors"
6 "fmt"
7 "os"
8 "path/filepath"
9 "runtime"
10 "strings"
11
12 "github.com/vektah/gqlparser/v2/ast"
13
14 "github.com/99designs/gqlgen/codegen/config"
15 "github.com/99designs/gqlgen/codegen/templates"
16 )
17
18
19 var codegenTemplates embed.FS
20
21 func GenerateCode(data *Data) error {
22 if !data.Config.Exec.IsDefined() {
23 return fmt.Errorf("missing exec config")
24 }
25
26 switch data.Config.Exec.Layout {
27 case config.ExecLayoutSingleFile:
28 return generateSingleFile(data)
29 case config.ExecLayoutFollowSchema:
30 return generatePerSchema(data)
31 }
32
33 return fmt.Errorf("unrecognized exec layout %s", data.Config.Exec.Layout)
34 }
35
36 func generateSingleFile(data *Data) error {
37 return templates.Render(templates.Options{
38 PackageName: data.Config.Exec.Package,
39 Filename: data.Config.Exec.Filename,
40 Data: data,
41 RegionTags: true,
42 GeneratedHeader: true,
43 Packages: data.Config.Packages,
44 TemplateFS: codegenTemplates,
45 })
46 }
47
48 func generatePerSchema(data *Data) error {
49 err := generateRootFile(data)
50 if err != nil {
51 return err
52 }
53
54 builds := map[string]*Data{}
55
56 err = addObjects(data, &builds)
57 if err != nil {
58 return err
59 }
60
61 err = addInputs(data, &builds)
62 if err != nil {
63 return err
64 }
65
66 err = addInterfaces(data, &builds)
67 if err != nil {
68 return err
69 }
70
71 err = addReferencedTypes(data, &builds)
72 if err != nil {
73 return err
74 }
75
76 for filename, build := range builds {
77 if filename == "" {
78 continue
79 }
80
81 dir := data.Config.Exec.DirName
82 path := filepath.Join(dir, filename)
83
84 err = templates.Render(templates.Options{
85 PackageName: data.Config.Exec.Package,
86 Filename: path,
87 Data: build,
88 RegionTags: true,
89 GeneratedHeader: true,
90 Packages: data.Config.Packages,
91 TemplateFS: codegenTemplates,
92 })
93 if err != nil {
94 return err
95 }
96 }
97
98 return nil
99 }
100
101 func filename(p *ast.Position, config *config.Config) string {
102 name := "common!"
103 if p != nil && p.Src != nil {
104 gqlname := filepath.Base(p.Src.Name)
105 ext := filepath.Ext(p.Src.Name)
106 name = strings.TrimSuffix(gqlname, ext)
107 }
108
109 filenameTempl := config.Exec.FilenameTemplate
110 if filenameTempl == "" {
111 filenameTempl = "{name}.generated.go"
112 }
113
114 return strings.ReplaceAll(filenameTempl, "{name}", name)
115 }
116
117 func addBuild(filename string, p *ast.Position, data *Data, builds *map[string]*Data) {
118 buildConfig := *data.Config
119 if p != nil {
120 buildConfig.Sources = []*ast.Source{p.Src}
121 }
122
123 (*builds)[filename] = &Data{
124 Config: &buildConfig,
125 QueryRoot: data.QueryRoot,
126 MutationRoot: data.MutationRoot,
127 SubscriptionRoot: data.SubscriptionRoot,
128 AllDirectives: data.AllDirectives,
129 }
130 }
131
132
133
134 func generateRootFile(data *Data) error {
135 dir := data.Config.Exec.DirName
136 path := filepath.Join(dir, "root_.generated.go")
137
138 _, thisFile, _, _ := runtime.Caller(0)
139 rootDir := filepath.Dir(thisFile)
140 templatePath := filepath.Join(rootDir, "root_.gotpl")
141 templateBytes, err := os.ReadFile(templatePath)
142 if err != nil {
143 return err
144 }
145 template := string(templateBytes)
146
147 return templates.Render(templates.Options{
148 PackageName: data.Config.Exec.Package,
149 Template: template,
150 Filename: path,
151 Data: data,
152 RegionTags: false,
153 GeneratedHeader: true,
154 Packages: data.Config.Packages,
155 TemplateFS: codegenTemplates,
156 })
157 }
158
159 func addObjects(data *Data, builds *map[string]*Data) error {
160 for _, o := range data.Objects {
161 filename := filename(o.Position, data.Config)
162 if (*builds)[filename] == nil {
163 addBuild(filename, o.Position, data, builds)
164 }
165
166 (*builds)[filename].Objects = append((*builds)[filename].Objects, o)
167 }
168 return nil
169 }
170
171 func addInputs(data *Data, builds *map[string]*Data) error {
172 for _, in := range data.Inputs {
173 filename := filename(in.Position, data.Config)
174 if (*builds)[filename] == nil {
175 addBuild(filename, in.Position, data, builds)
176 }
177
178 (*builds)[filename].Inputs = append((*builds)[filename].Inputs, in)
179 }
180 return nil
181 }
182
183 func addInterfaces(data *Data, builds *map[string]*Data) error {
184 for k, inf := range data.Interfaces {
185 filename := filename(inf.Position, data.Config)
186 if (*builds)[filename] == nil {
187 addBuild(filename, inf.Position, data, builds)
188 }
189 build := (*builds)[filename]
190
191 if build.Interfaces == nil {
192 build.Interfaces = map[string]*Interface{}
193 }
194 if build.Interfaces[k] != nil {
195 return errors.New("conflicting interface keys")
196 }
197
198 build.Interfaces[k] = inf
199 }
200 return nil
201 }
202
203 func addReferencedTypes(data *Data, builds *map[string]*Data) error {
204 for k, rt := range data.ReferencedTypes {
205 filename := filename(rt.Definition.Position, data.Config)
206 if (*builds)[filename] == nil {
207 addBuild(filename, rt.Definition.Position, data, builds)
208 }
209 build := (*builds)[filename]
210
211 if build.ReferencedTypes == nil {
212 build.ReferencedTypes = map[string]*config.TypeReference{}
213 }
214 if build.ReferencedTypes[k] != nil {
215 return errors.New("conflicting referenced type keys")
216 }
217
218 build.ReferencedTypes[k] = rt
219 }
220 return nil
221 }
222
View as plain text