build.go

  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}