1package codegen
2
3import (
4 "fmt"
5 "go/build"
6 "go/types"
7 "os"
8
9 "github.com/pkg/errors"
10 "golang.org/x/tools/go/loader"
11)
12
13type Build struct {
14 PackageName string
15 Objects Objects
16 Inputs Objects
17 Interfaces []*Interface
18 Imports []*Import
19 QueryRoot *Object
20 MutationRoot *Object
21 SubscriptionRoot *Object
22 SchemaRaw string
23 SchemaFilename string
24 Directives []*Directive
25}
26
27type ModelBuild struct {
28 PackageName string
29 Imports []*Import
30 Models []Model
31 Enums []Enum
32}
33
34type ResolverBuild struct {
35 PackageName string
36 Imports []*Import
37 ResolverType string
38 Objects Objects
39 ResolverFound bool
40}
41
42type ServerBuild struct {
43 PackageName string
44 Imports []*Import
45 ExecPackageName string
46 ResolverPackageName string
47}
48
49// Create a list of models that need to be generated
50func (cfg *Config) models() (*ModelBuild, error) {
51 namedTypes := cfg.buildNamedTypes()
52
53 progLoader := newLoader(namedTypes, true)
54 prog, err := progLoader.Load()
55 if err != nil {
56 return nil, errors.Wrap(err, "loading failed")
57 }
58 imports := buildImports(namedTypes, cfg.Model.Dir())
59
60 cfg.bindTypes(imports, namedTypes, cfg.Model.Dir(), prog)
61
62 models, err := cfg.buildModels(namedTypes, prog, imports)
63 if err != nil {
64 return nil, err
65 }
66 return &ModelBuild{
67 PackageName: cfg.Model.Package,
68 Models: models,
69 Enums: cfg.buildEnums(namedTypes),
70 Imports: imports.finalize(),
71 }, nil
72}
73
74// bind a schema together with some code to generate a Build
75func (cfg *Config) resolver() (*ResolverBuild, error) {
76 progLoader := newLoader(cfg.buildNamedTypes(), true)
77 progLoader.Import(cfg.Resolver.ImportPath())
78
79 prog, err := progLoader.Load()
80 if err != nil {
81 return nil, err
82 }
83
84 destDir := cfg.Resolver.Dir()
85
86 namedTypes := cfg.buildNamedTypes()
87 imports := buildImports(namedTypes, destDir)
88 imports.add(cfg.Exec.ImportPath())
89 imports.add("github.com/99designs/gqlgen/handler") // avoid import github.com/vektah/gqlgen/handler
90
91 cfg.bindTypes(imports, namedTypes, destDir, prog)
92
93 objects, err := cfg.buildObjects(namedTypes, prog, imports)
94 if err != nil {
95 return nil, err
96 }
97
98 def, _ := findGoType(prog, cfg.Resolver.ImportPath(), cfg.Resolver.Type)
99 resolverFound := def != nil
100
101 return &ResolverBuild{
102 PackageName: cfg.Resolver.Package,
103 Imports: imports.finalize(),
104 Objects: objects,
105 ResolverType: cfg.Resolver.Type,
106 ResolverFound: resolverFound,
107 }, nil
108}
109
110func (cfg *Config) server(destDir string) *ServerBuild {
111 imports := buildImports(NamedTypes{}, destDir)
112 imports.add(cfg.Exec.ImportPath())
113 imports.add(cfg.Resolver.ImportPath())
114
115 return &ServerBuild{
116 PackageName: cfg.Resolver.Package,
117 Imports: imports.finalize(),
118 ExecPackageName: cfg.Exec.Package,
119 ResolverPackageName: cfg.Resolver.Package,
120 }
121}
122
123// bind a schema together with some code to generate a Build
124func (cfg *Config) bind() (*Build, error) {
125 namedTypes := cfg.buildNamedTypes()
126
127 progLoader := newLoader(namedTypes, true)
128 prog, err := progLoader.Load()
129 if err != nil {
130 return nil, errors.Wrap(err, "loading failed")
131 }
132
133 imports := buildImports(namedTypes, cfg.Exec.Dir())
134 cfg.bindTypes(imports, namedTypes, cfg.Exec.Dir(), prog)
135
136 objects, err := cfg.buildObjects(namedTypes, prog, imports)
137 if err != nil {
138 return nil, err
139 }
140
141 inputs, err := cfg.buildInputs(namedTypes, prog, imports)
142 if err != nil {
143 return nil, err
144 }
145 directives, err := cfg.buildDirectives(namedTypes)
146 if err != nil {
147 return nil, err
148 }
149
150 b := &Build{
151 PackageName: cfg.Exec.Package,
152 Objects: objects,
153 Interfaces: cfg.buildInterfaces(namedTypes, prog),
154 Inputs: inputs,
155 Imports: imports.finalize(),
156 SchemaRaw: cfg.SchemaStr,
157 SchemaFilename: cfg.SchemaFilename,
158 Directives: directives,
159 }
160
161 if cfg.schema.Query != nil {
162 b.QueryRoot = b.Objects.ByName(cfg.schema.Query.Name)
163 } else {
164 return b, fmt.Errorf("query entry point missing")
165 }
166
167 if cfg.schema.Mutation != nil {
168 b.MutationRoot = b.Objects.ByName(cfg.schema.Mutation.Name)
169 }
170
171 if cfg.schema.Subscription != nil {
172 b.SubscriptionRoot = b.Objects.ByName(cfg.schema.Subscription.Name)
173 }
174 return b, nil
175}
176
177func (cfg *Config) validate() error {
178 progLoader := newLoader(cfg.buildNamedTypes(), false)
179 _, err := progLoader.Load()
180 return err
181}
182
183func newLoader(namedTypes NamedTypes, allowErrors bool) loader.Config {
184 conf := loader.Config{}
185 if allowErrors {
186 conf = loader.Config{
187 AllowErrors: true,
188 TypeChecker: types.Config{
189 Error: func(e error) {},
190 },
191 }
192 }
193 for _, imp := range ambientImports {
194 conf.Import(imp)
195 }
196
197 for _, imp := range namedTypes {
198 if imp.Package != "" {
199 conf.Import(imp.Package)
200 }
201 }
202 return conf
203}
204
205func resolvePkg(pkgName string) (string, error) {
206 cwd, _ := os.Getwd()
207
208 pkg, err := build.Default.Import(pkgName, cwd, build.FindOnly)
209 if err != nil {
210 return "", err
211 }
212
213 return pkg.ImportPath, nil
214}