github.com/plages-ai/gqlgen@v0.7.2/codegen/config.go (about) 1 package codegen 2 3 import ( 4 "fmt" 5 "go/build" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "sort" 10 "strings" 11 12 "github.com/99designs/gqlgen/internal/gopath" 13 "github.com/pkg/errors" 14 "github.com/vektah/gqlparser/ast" 15 "gopkg.in/yaml.v2" 16 ) 17 18 var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"} 19 20 // DefaultConfig creates a copy of the default config 21 func DefaultConfig() *Config { 22 return &Config{ 23 SchemaFilename: SchemaFilenames{"schema.graphql"}, 24 SchemaStr: map[string]string{}, 25 Model: PackageConfig{Filename: "models_gen.go"}, 26 Exec: PackageConfig{Filename: "generated.go"}, 27 } 28 } 29 30 // LoadConfigFromDefaultLocations looks for a config file in the current directory, and all parent directories 31 // walking up the tree. The closest config file will be returned. 32 func LoadConfigFromDefaultLocations() (*Config, error) { 33 cfgFile, err := findCfg() 34 if err != nil { 35 return nil, err 36 } 37 38 err = os.Chdir(filepath.Dir(cfgFile)) 39 if err != nil { 40 return nil, errors.Wrap(err, "unable to enter config dir") 41 } 42 return LoadConfig(cfgFile) 43 } 44 45 // LoadConfig reads the gqlgen.yml config file 46 func LoadConfig(filename string) (*Config, error) { 47 config := DefaultConfig() 48 49 b, err := ioutil.ReadFile(filename) 50 if err != nil { 51 return nil, errors.Wrap(err, "unable to read config") 52 } 53 54 if err := yaml.UnmarshalStrict(b, config); err != nil { 55 return nil, errors.Wrap(err, "unable to parse config") 56 } 57 58 preGlobbing := config.SchemaFilename 59 config.SchemaFilename = SchemaFilenames{} 60 for _, f := range preGlobbing { 61 matches, err := filepath.Glob(f) 62 if err != nil { 63 return nil, errors.Wrapf(err, "failed to glob schema filename %s", f) 64 } 65 66 for _, m := range matches { 67 if config.SchemaFilename.Has(m) { 68 continue 69 } 70 config.SchemaFilename = append(config.SchemaFilename, m) 71 } 72 } 73 74 config.FilePath = filename 75 config.SchemaStr = map[string]string{} 76 77 return config, nil 78 } 79 80 type Config struct { 81 SchemaFilename SchemaFilenames `yaml:"schema,omitempty"` 82 SchemaStr map[string]string `yaml:"-"` 83 Exec PackageConfig `yaml:"exec"` 84 Model PackageConfig `yaml:"model"` 85 Resolver PackageConfig `yaml:"resolver,omitempty"` 86 Models TypeMap `yaml:"models,omitempty"` 87 StructTag string `yaml:"struct_tag,omitempty"` 88 89 FilePath string `yaml:"-"` 90 91 schema *ast.Schema `yaml:"-"` 92 } 93 94 type PackageConfig struct { 95 Filename string `yaml:"filename,omitempty"` 96 Package string `yaml:"package,omitempty"` 97 Type string `yaml:"type,omitempty"` 98 } 99 100 type TypeMapEntry struct { 101 Model string `yaml:"model"` 102 Fields map[string]TypeMapField `yaml:"fields,omitempty"` 103 } 104 105 type TypeMapField struct { 106 Resolver bool `yaml:"resolver"` 107 FieldName string `yaml:"fieldName"` 108 } 109 110 type SchemaFilenames []string 111 112 func (a *SchemaFilenames) UnmarshalYAML(unmarshal func(interface{}) error) error { 113 var single string 114 err := unmarshal(&single) 115 if err == nil { 116 *a = []string{single} 117 return nil 118 } 119 120 var multi []string 121 err = unmarshal(&multi) 122 if err != nil { 123 return err 124 } 125 126 *a = multi 127 return nil 128 } 129 130 func (a SchemaFilenames) Has(file string) bool { 131 for _, existing := range a { 132 if existing == file { 133 return true 134 } 135 } 136 return false 137 } 138 139 func (c *PackageConfig) normalize() error { 140 if c.Filename == "" { 141 return errors.New("Filename is required") 142 } 143 c.Filename = abs(c.Filename) 144 // If Package is not set, first attempt to load the package at the output dir. If that fails 145 // fallback to just the base dir name of the output filename. 146 if c.Package == "" { 147 cwd, _ := os.Getwd() 148 pkg, _ := build.Default.Import(c.ImportPath(), cwd, 0) 149 if pkg.Name != "" { 150 c.Package = pkg.Name 151 } else { 152 c.Package = filepath.Base(c.Dir()) 153 } 154 } 155 c.Package = sanitizePackageName(c.Package) 156 return nil 157 } 158 159 func (c *PackageConfig) ImportPath() string { 160 return gopath.MustDir2Import(c.Dir()) 161 } 162 163 func (c *PackageConfig) Dir() string { 164 return filepath.Dir(c.Filename) 165 } 166 167 func (c *PackageConfig) Check() error { 168 if strings.ContainsAny(c.Package, "./\\") { 169 return fmt.Errorf("package should be the output package name only, do not include the output filename") 170 } 171 if c.Filename != "" && !strings.HasSuffix(c.Filename, ".go") { 172 return fmt.Errorf("filename should be path to a go source file") 173 } 174 return nil 175 } 176 177 func (c *PackageConfig) IsDefined() bool { 178 return c.Filename != "" 179 } 180 181 func (cfg *Config) Check() error { 182 if err := cfg.Models.Check(); err != nil { 183 return errors.Wrap(err, "config.models") 184 } 185 if err := cfg.Exec.Check(); err != nil { 186 return errors.Wrap(err, "config.exec") 187 } 188 if err := cfg.Model.Check(); err != nil { 189 return errors.Wrap(err, "config.model") 190 } 191 if err := cfg.Resolver.Check(); err != nil { 192 return errors.Wrap(err, "config.resolver") 193 } 194 return nil 195 } 196 197 type TypeMap map[string]TypeMapEntry 198 199 func (tm TypeMap) Exists(typeName string) bool { 200 _, ok := tm[typeName] 201 return ok 202 } 203 204 func (tm TypeMap) Check() error { 205 for typeName, entry := range tm { 206 if strings.LastIndex(entry.Model, ".") < strings.LastIndex(entry.Model, "/") { 207 return fmt.Errorf("model %s: invalid type specifier \"%s\" - you need to specify a struct to map to", typeName, entry.Model) 208 } 209 } 210 return nil 211 } 212 213 func (tm TypeMap) referencedPackages() []string { 214 var pkgs []string 215 216 for _, typ := range tm { 217 if typ.Model == "map[string]interface{}" { 218 continue 219 } 220 pkg, _ := pkgAndType(typ.Model) 221 if pkg == "" || inStrSlice(pkgs, pkg) { 222 continue 223 } 224 pkgs = append(pkgs, pkg) 225 } 226 227 sort.Slice(pkgs, func(i, j int) bool { 228 return pkgs[i] > pkgs[j] 229 }) 230 return pkgs 231 } 232 233 func inStrSlice(haystack []string, needle string) bool { 234 for _, v := range haystack { 235 if needle == v { 236 return true 237 } 238 } 239 240 return false 241 } 242 243 // findCfg searches for the config file in this directory and all parents up the tree 244 // looking for the closest match 245 func findCfg() (string, error) { 246 dir, err := os.Getwd() 247 if err != nil { 248 return "", errors.Wrap(err, "unable to get working dir to findCfg") 249 } 250 251 cfg := findCfgInDir(dir) 252 253 for cfg == "" && dir != filepath.Dir(dir) { 254 dir = filepath.Dir(dir) 255 cfg = findCfgInDir(dir) 256 } 257 258 if cfg == "" { 259 return "", os.ErrNotExist 260 } 261 262 return cfg, nil 263 } 264 265 func findCfgInDir(dir string) string { 266 for _, cfgName := range cfgFilenames { 267 path := filepath.Join(dir, cfgName) 268 if _, err := os.Stat(path); err == nil { 269 return path 270 } 271 } 272 return "" 273 }