config.go

  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}