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}
24
25type ModelBuild struct {
26 PackageName string
27 Imports []*Import
28 Models []Model
29 Enums []Enum
30}
31
32// Create a list of models that need to be generated
33func (cfg *Config) models() (*ModelBuild, error) {
34 namedTypes := cfg.buildNamedTypes()
35
36 prog, err := cfg.loadProgram(namedTypes, true)
37 if err != nil {
38 return nil, errors.Wrap(err, "loading failed")
39 }
40 imports := buildImports(namedTypes, cfg.Model.Dir())
41
42 cfg.bindTypes(imports, namedTypes, cfg.Model.Dir(), prog)
43
44 models, err := cfg.buildModels(namedTypes, prog)
45 if err != nil {
46 return nil, err
47 }
48 return &ModelBuild{
49 PackageName: cfg.Model.Package,
50 Models: models,
51 Enums: cfg.buildEnums(namedTypes),
52 Imports: imports.finalize(),
53 }, nil
54}
55
56// bind a schema together with some code to generate a Build
57func (cfg *Config) bind() (*Build, error) {
58 namedTypes := cfg.buildNamedTypes()
59
60 prog, err := cfg.loadProgram(namedTypes, true)
61 if err != nil {
62 return nil, errors.Wrap(err, "loading failed")
63 }
64
65 imports := buildImports(namedTypes, cfg.Exec.Dir())
66 cfg.bindTypes(imports, namedTypes, cfg.Exec.Dir(), prog)
67
68 objects, err := cfg.buildObjects(namedTypes, prog, imports)
69 if err != nil {
70 return nil, err
71 }
72
73 inputs, err := cfg.buildInputs(namedTypes, prog, imports)
74 if err != nil {
75 return nil, err
76 }
77
78 b := &Build{
79 PackageName: cfg.Exec.Package,
80 Objects: objects,
81 Interfaces: cfg.buildInterfaces(namedTypes, prog),
82 Inputs: inputs,
83 Imports: imports.finalize(),
84 SchemaRaw: cfg.SchemaStr,
85 }
86
87 if qr, ok := cfg.schema.EntryPoints["query"]; ok {
88 b.QueryRoot = b.Objects.ByName(qr.TypeName())
89 }
90
91 if mr, ok := cfg.schema.EntryPoints["mutation"]; ok {
92 b.MutationRoot = b.Objects.ByName(mr.TypeName())
93 }
94
95 if sr, ok := cfg.schema.EntryPoints["subscription"]; ok {
96 b.SubscriptionRoot = b.Objects.ByName(sr.TypeName())
97 }
98
99 if b.QueryRoot == nil {
100 return b, fmt.Errorf("query entry point missing")
101 }
102
103 // Poke a few magic methods into query
104 q := b.Objects.ByName(b.QueryRoot.GQLType)
105 q.Fields = append(q.Fields, Field{
106 Type: &Type{namedTypes["__Schema"], []string{modPtr}, nil},
107 GQLName: "__schema",
108 NoErr: true,
109 GoMethodName: "ec.introspectSchema",
110 Object: q,
111 })
112 q.Fields = append(q.Fields, Field{
113 Type: &Type{namedTypes["__Type"], []string{modPtr}, nil},
114 GQLName: "__type",
115 NoErr: true,
116 GoMethodName: "ec.introspectType",
117 Args: []FieldArgument{
118 {GQLName: "name", Type: &Type{namedTypes["String"], []string{}, nil}, Object: &Object{}},
119 },
120 Object: q,
121 })
122
123 return b, nil
124}
125
126func (cfg *Config) validate() error {
127 namedTypes := cfg.buildNamedTypes()
128
129 _, err := cfg.loadProgram(namedTypes, false)
130 return err
131}
132
133func (cfg *Config) loadProgram(namedTypes NamedTypes, allowErrors bool) (*loader.Program, error) {
134 conf := loader.Config{}
135 if allowErrors {
136 conf = loader.Config{
137 AllowErrors: true,
138 TypeChecker: types.Config{
139 Error: func(e error) {},
140 },
141 }
142 }
143 for _, imp := range ambientImports {
144 conf.Import(imp)
145 }
146
147 for _, imp := range namedTypes {
148 if imp.Package != "" {
149 conf.Import(imp.Package)
150 }
151 }
152
153 return conf.Load()
154}
155
156func resolvePkg(pkgName string) (string, error) {
157 cwd, _ := os.Getwd()
158
159 pkg, err := build.Default.Import(pkgName, cwd, build.FindOnly)
160 if err != nil {
161 return "", err
162 }
163
164 return pkg.ImportPath, nil
165}