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