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}