1package codegen
2
3import (
4 "fmt"
5 "go/build"
6 "io/ioutil"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "github.com/pkg/errors"
12 "github.com/vektah/gqlgen/neelance/schema"
13 "gopkg.in/yaml.v2"
14)
15
16var defaults = Config{
17 SchemaFilename: "schema.graphql",
18 Model: PackageConfig{Filename: "models_gen.go"},
19 Exec: PackageConfig{Filename: "generated.go"},
20}
21
22var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"}
23
24// LoadDefaultConfig looks for a config file in the current directory, and all parent directories
25// walking up the tree. The closest config file will be returned.
26func LoadDefaultConfig() (*Config, error) {
27 cfgFile, err := findCfg()
28 if err != nil || cfgFile == "" {
29 cpy := defaults
30 return &cpy, err
31 }
32
33 err = os.Chdir(filepath.Dir(cfgFile))
34 if err != nil {
35 return nil, errors.Wrap(err, "unable to enter config dir")
36 }
37 return LoadConfig(cfgFile)
38}
39
40// LoadConfig reads the gqlgen.yml config file
41func LoadConfig(filename string) (*Config, error) {
42 config := defaults
43
44 b, err := ioutil.ReadFile(filename)
45 if err != nil {
46 return nil, errors.Wrap(err, "unable to read config")
47 }
48
49 if err := yaml.UnmarshalStrict(b, &config); err != nil {
50 return nil, errors.Wrap(err, "unable to parse config")
51 }
52
53 return &config, nil
54}
55
56type Config struct {
57 SchemaFilename string `yaml:"schema,omitempty"`
58 SchemaStr string `yaml:"-"`
59 Exec PackageConfig `yaml:"exec"`
60 Model PackageConfig `yaml:"model"`
61 Models TypeMap `yaml:"models,omitempty"`
62
63 schema *schema.Schema `yaml:"-"`
64}
65
66type PackageConfig struct {
67 Filename string `yaml:"filename,omitempty"`
68 Package string `yaml:"package,omitempty"`
69}
70
71type TypeMapEntry struct {
72 Model string `yaml:"model"`
73 Fields map[string]TypeMapField `yaml:"fields,omitempty"`
74}
75
76type TypeMapField struct {
77 Resolver bool `yaml:"resolver"`
78}
79
80func (c *PackageConfig) normalize() error {
81 if c.Filename == "" {
82 return errors.New("Filename is required")
83 }
84 c.Filename = abs(c.Filename)
85 // If Package is not set, first attempt to load the package at the output dir. If that fails
86 // fallback to just the base dir name of the output filename.
87 if c.Package == "" {
88 cwd, _ := os.Getwd()
89 pkg, _ := build.Default.Import(c.ImportPath(), cwd, 0)
90 if pkg.Name != "" {
91 c.Package = pkg.Name
92 } else {
93 c.Package = filepath.Base(c.Dir())
94 }
95 }
96 c.Package = sanitizePackageName(c.Package)
97 return nil
98}
99
100func (c *PackageConfig) ImportPath() string {
101 dir := filepath.ToSlash(c.Dir())
102 for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
103 gopath = filepath.ToSlash(gopath) + "/src/"
104 if len(gopath) > len(dir) {
105 continue
106 }
107 if strings.EqualFold(gopath, dir[0:len(gopath)]) {
108 dir = dir[len(gopath):]
109 break
110 }
111 }
112 return dir
113}
114
115func (c *PackageConfig) Dir() string {
116 return filepath.ToSlash(filepath.Dir(c.Filename))
117}
118
119func (c *PackageConfig) Check() error {
120 if strings.ContainsAny(c.Package, "./\\") {
121 return fmt.Errorf("package should be the output package name only, do not include the output filename")
122 }
123 if c.Filename != "" && !strings.HasSuffix(c.Filename, ".go") {
124 return fmt.Errorf("filename should be path to a go source file")
125 }
126 return nil
127}
128
129func (cfg *Config) Check() error {
130 if err := cfg.Models.Check(); err != nil {
131 return errors.Wrap(err, "config.models")
132 }
133 if err := cfg.Exec.Check(); err != nil {
134 return errors.Wrap(err, "config.exec")
135 }
136 if err := cfg.Model.Check(); err != nil {
137 return errors.Wrap(err, "config.model")
138 }
139 return nil
140}
141
142type TypeMap map[string]TypeMapEntry
143
144func (tm TypeMap) Exists(typeName string) bool {
145 _, ok := tm[typeName]
146 return ok
147}
148
149func (tm TypeMap) Check() error {
150 for typeName, entry := range tm {
151 if strings.LastIndex(entry.Model, ".") < strings.LastIndex(entry.Model, "/") {
152 return fmt.Errorf("model %s: invalid type specifier \"%s\" - you need to specify a struct to map to", typeName, entry.Model)
153 }
154 }
155 return nil
156}
157
158// findCfg searches for the config file in this directory and all parents up the tree
159// looking for the closest match
160func findCfg() (string, error) {
161 dir, err := os.Getwd()
162 if err != nil {
163 return "", errors.Wrap(err, "unable to get working dir to findCfg")
164 }
165
166 cfg := findCfgInDir(dir)
167
168 for cfg == "" && dir != filepath.Dir(dir) {
169 dir = filepath.Dir(dir)
170 cfg = findCfgInDir(dir)
171 }
172
173 return cfg, nil
174}
175
176func findCfgInDir(dir string) string {
177 for _, cfgName := range cfgFilenames {
178 path := filepath.Join(dir, cfgName)
179 if _, err := os.Stat(path); err == nil {
180 return path
181 }
182 }
183 return ""
184}