package codegen
import (
"fmt"
"go/build"
"go/types"
"os"
"github.com/pkg/errors"
"golang.org/x/tools/go/loader"
)
type Build struct {
PackageName string
Objects Objects
Inputs Objects
Interfaces []*Interface
Imports []*Import
QueryRoot *Object
MutationRoot *Object
SubscriptionRoot *Object
SchemaRaw string
}
type ModelBuild struct {
PackageName string
Imports []*Import
Models []Model
Enums []Enum
}
// Create a list of models that need to be generated
func (cfg *Config) models() (*ModelBuild, error) {
namedTypes := cfg.buildNamedTypes()
prog, err := cfg.loadProgram(namedTypes, true)
if err != nil {
return nil, errors.Wrap(err, "loading failed")
}
imports := buildImports(namedTypes, cfg.Model.Dir())
cfg.bindTypes(imports, namedTypes, cfg.Model.Dir(), prog)
models, err := cfg.buildModels(namedTypes, prog)
if err != nil {
return nil, err
}
return &ModelBuild{
PackageName: cfg.Model.Package,
Models: models,
Enums: cfg.buildEnums(namedTypes),
Imports: imports.finalize(),
}, nil
}
// bind a schema together with some code to generate a Build
func (cfg *Config) bind() (*Build, error) {
namedTypes := cfg.buildNamedTypes()
prog, err := cfg.loadProgram(namedTypes, true)
if err != nil {
return nil, errors.Wrap(err, "loading failed")
}
imports := buildImports(namedTypes, cfg.Exec.Dir())
cfg.bindTypes(imports, namedTypes, cfg.Exec.Dir(), prog)
objects, err := cfg.buildObjects(namedTypes, prog, imports)
if err != nil {
return nil, err
}
inputs, err := cfg.buildInputs(namedTypes, prog, imports)
if err != nil {
return nil, err
}
b := &Build{
PackageName: cfg.Exec.Package,
Objects: objects,
Interfaces: cfg.buildInterfaces(namedTypes, prog),
Inputs: inputs,
Imports: imports.finalize(),
SchemaRaw: cfg.SchemaStr,
}
if qr, ok := cfg.schema.EntryPoints["query"]; ok {
b.QueryRoot = b.Objects.ByName(qr.TypeName())
}
if mr, ok := cfg.schema.EntryPoints["mutation"]; ok {
b.MutationRoot = b.Objects.ByName(mr.TypeName())
}
if sr, ok := cfg.schema.EntryPoints["subscription"]; ok {
b.SubscriptionRoot = b.Objects.ByName(sr.TypeName())
}
if b.QueryRoot == nil {
return b, fmt.Errorf("query entry point missing")
}
// Poke a few magic methods into query
q := b.Objects.ByName(b.QueryRoot.GQLType)
q.Fields = append(q.Fields, Field{
Type: &Type{namedTypes["__Schema"], []string{modPtr}, nil},
GQLName: "__schema",
NoErr: true,
GoMethodName: "ec.introspectSchema",
Object: q,
})
q.Fields = append(q.Fields, Field{
Type: &Type{namedTypes["__Type"], []string{modPtr}, nil},
GQLName: "__type",
NoErr: true,
GoMethodName: "ec.introspectType",
Args: []FieldArgument{
{GQLName: "name", Type: &Type{namedTypes["String"], []string{}, nil}, Object: &Object{}},
},
Object: q,
})
return b, nil
}
func (cfg *Config) validate() error {
namedTypes := cfg.buildNamedTypes()
_, err := cfg.loadProgram(namedTypes, false)
return err
}
func (cfg *Config) loadProgram(namedTypes NamedTypes, allowErrors bool) (*loader.Program, error) {
conf := loader.Config{}
if allowErrors {
conf = loader.Config{
AllowErrors: true,
TypeChecker: types.Config{
Error: func(e error) {},
},
}
}
for _, imp := range ambientImports {
conf.Import(imp)
}
for _, imp := range namedTypes {
if imp.Package != "" {
conf.Import(imp.Package)
}
}
return conf.Load()
}
func resolvePkg(pkgName string) (string, error) {
cwd, _ := os.Getwd()
pkg, err := build.Default.Import(pkgName, cwd, build.FindOnly)
if err != nil {
return "", err
}
return pkg.ImportPath, nil
}