diff options
author | Amine Hilaly <hilalyamine@gmail.com> | 2019-04-09 21:45:24 +0200 |
---|---|---|
committer | Amine Hilaly <hilalyamine@gmail.com> | 2019-04-09 21:45:24 +0200 |
commit | 26b5343e2160de172969e6834074cf8482ceb845 (patch) | |
tree | 04f27aa660a903d65f7b3d951bd1c6f92c59c0c3 /vendor/github.com/99designs/gqlgen/codegen/config/config.go | |
parent | 6e8496f4c1767ca8a8b95716a04f1b492bef7397 (diff) | |
download | git-bug-26b5343e2160de172969e6834074cf8482ceb845.tar.gz |
Update Gopkg.*
Diffstat (limited to 'vendor/github.com/99designs/gqlgen/codegen/config/config.go')
-rw-r--r-- | vendor/github.com/99designs/gqlgen/codegen/config/config.go | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/vendor/github.com/99designs/gqlgen/codegen/config/config.go b/vendor/github.com/99designs/gqlgen/codegen/config/config.go new file mode 100644 index 00000000..0c72420e --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/codegen/config/config.go @@ -0,0 +1,408 @@ +package config + +import ( + "fmt" + "go/types" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/99designs/gqlgen/internal/code" + "github.com/pkg/errors" + "github.com/vektah/gqlparser" + "github.com/vektah/gqlparser/ast" + yaml "gopkg.in/yaml.v2" +) + +type Config struct { + SchemaFilename StringList `yaml:"schema,omitempty"` + Exec PackageConfig `yaml:"exec"` + Model PackageConfig `yaml:"model"` + Resolver PackageConfig `yaml:"resolver,omitempty"` + Models TypeMap `yaml:"models,omitempty"` + StructTag string `yaml:"struct_tag,omitempty"` +} + +var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"} + +// DefaultConfig creates a copy of the default config +func DefaultConfig() *Config { + return &Config{ + SchemaFilename: StringList{"schema.graphql"}, + Model: PackageConfig{Filename: "models_gen.go"}, + Exec: PackageConfig{Filename: "generated.go"}, + } +} + +// LoadConfigFromDefaultLocations looks for a config file in the current directory, and all parent directories +// walking up the tree. The closest config file will be returned. +func LoadConfigFromDefaultLocations() (*Config, error) { + cfgFile, err := findCfg() + if err != nil { + return nil, err + } + + err = os.Chdir(filepath.Dir(cfgFile)) + if err != nil { + return nil, errors.Wrap(err, "unable to enter config dir") + } + return LoadConfig(cfgFile) +} + +// LoadConfig reads the gqlgen.yml config file +func LoadConfig(filename string) (*Config, error) { + config := DefaultConfig() + + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, errors.Wrap(err, "unable to read config") + } + + if err := yaml.UnmarshalStrict(b, config); err != nil { + return nil, errors.Wrap(err, "unable to parse config") + } + + preGlobbing := config.SchemaFilename + config.SchemaFilename = StringList{} + for _, f := range preGlobbing { + matches, err := filepath.Glob(f) + if err != nil { + return nil, errors.Wrapf(err, "failed to glob schema filename %s", f) + } + + for _, m := range matches { + if config.SchemaFilename.Has(m) { + continue + } + config.SchemaFilename = append(config.SchemaFilename, m) + } + } + + return config, nil +} + +type PackageConfig struct { + Filename string `yaml:"filename,omitempty"` + Package string `yaml:"package,omitempty"` + Type string `yaml:"type,omitempty"` +} + +type TypeMapEntry struct { + Model StringList `yaml:"model"` + Fields map[string]TypeMapField `yaml:"fields,omitempty"` +} + +type TypeMapField struct { + Resolver bool `yaml:"resolver"` + FieldName string `yaml:"fieldName"` +} + +type StringList []string + +func (a *StringList) UnmarshalYAML(unmarshal func(interface{}) error) error { + var single string + err := unmarshal(&single) + if err == nil { + *a = []string{single} + return nil + } + + var multi []string + err = unmarshal(&multi) + if err != nil { + return err + } + + *a = multi + return nil +} + +func (a StringList) Has(file string) bool { + for _, existing := range a { + if existing == file { + return true + } + } + return false +} + +func (c *PackageConfig) normalize() error { + if c.Filename == "" { + return errors.New("Filename is required") + } + c.Filename = abs(c.Filename) + // If Package is not set, first attempt to load the package at the output dir. If that fails + // fallback to just the base dir name of the output filename. + if c.Package == "" { + c.Package = code.NameForPackage(c.ImportPath()) + } + + return nil +} + +func (c *PackageConfig) ImportPath() string { + return code.ImportPathForDir(c.Dir()) +} + +func (c *PackageConfig) Dir() string { + return filepath.Dir(c.Filename) +} + +func (c *PackageConfig) Check() error { + if strings.ContainsAny(c.Package, "./\\") { + return fmt.Errorf("package should be the output package name only, do not include the output filename") + } + if c.Filename != "" && !strings.HasSuffix(c.Filename, ".go") { + return fmt.Errorf("filename should be path to a go source file") + } + + return c.normalize() +} + +func (c *PackageConfig) Pkg() *types.Package { + return types.NewPackage(c.ImportPath(), c.Dir()) +} + +func (c *PackageConfig) IsDefined() bool { + return c.Filename != "" +} + +func (c *Config) Check() error { + if err := c.Models.Check(); err != nil { + return errors.Wrap(err, "config.models") + } + if err := c.Exec.Check(); err != nil { + return errors.Wrap(err, "config.exec") + } + if err := c.Model.Check(); err != nil { + return errors.Wrap(err, "config.model") + } + if c.Resolver.IsDefined() { + if err := c.Resolver.Check(); err != nil { + return errors.Wrap(err, "config.resolver") + } + } + + // check packages names against conflict, if present in the same dir + // and check filenames for uniqueness + packageConfigList := []PackageConfig{ + c.Model, + c.Exec, + c.Resolver, + } + filesMap := make(map[string]bool) + pkgConfigsByDir := make(map[string]PackageConfig) + for _, current := range packageConfigList { + _, fileFound := filesMap[current.Filename] + if fileFound { + return fmt.Errorf("filename %s defined more than once", current.Filename) + } + filesMap[current.Filename] = true + previous, inSameDir := pkgConfigsByDir[current.Dir()] + if inSameDir && current.Package != previous.Package { + return fmt.Errorf("filenames %s and %s are in the same directory but have different package definitions", stripPath(current.Filename), stripPath(previous.Filename)) + } + pkgConfigsByDir[current.Dir()] = current + } + + return c.normalize() +} + +func stripPath(path string) string { + return filepath.Base(path) +} + +type TypeMap map[string]TypeMapEntry + +func (tm TypeMap) Exists(typeName string) bool { + _, ok := tm[typeName] + return ok +} + +func (tm TypeMap) UserDefined(typeName string) bool { + m, ok := tm[typeName] + return ok && len(m.Model) > 0 +} + +func (tm TypeMap) Check() error { + for typeName, entry := range tm { + for _, model := range entry.Model { + if strings.LastIndex(model, ".") < strings.LastIndex(model, "/") { + return fmt.Errorf("model %s: invalid type specifier \"%s\" - you need to specify a struct to map to", typeName, entry.Model) + } + } + } + return nil +} + +func (tm TypeMap) ReferencedPackages() []string { + var pkgs []string + + for _, typ := range tm { + for _, model := range typ.Model { + if model == "map[string]interface{}" || model == "interface{}" { + continue + } + pkg, _ := code.PkgAndType(model) + if pkg == "" || inStrSlice(pkgs, pkg) { + continue + } + pkgs = append(pkgs, code.QualifyPackagePath(pkg)) + } + } + + sort.Slice(pkgs, func(i, j int) bool { + return pkgs[i] > pkgs[j] + }) + return pkgs +} + +func (tm TypeMap) Add(Name string, goType string) { + modelCfg := tm[Name] + modelCfg.Model = append(modelCfg.Model, goType) + tm[Name] = modelCfg +} + +func inStrSlice(haystack []string, needle string) bool { + for _, v := range haystack { + if needle == v { + return true + } + } + + return false +} + +// findCfg searches for the config file in this directory and all parents up the tree +// looking for the closest match +func findCfg() (string, error) { + dir, err := os.Getwd() + if err != nil { + return "", errors.Wrap(err, "unable to get working dir to findCfg") + } + + cfg := findCfgInDir(dir) + + for cfg == "" && dir != filepath.Dir(dir) { + dir = filepath.Dir(dir) + cfg = findCfgInDir(dir) + } + + if cfg == "" { + return "", os.ErrNotExist + } + + return cfg, nil +} + +func findCfgInDir(dir string) string { + for _, cfgName := range cfgFilenames { + path := filepath.Join(dir, cfgName) + if _, err := os.Stat(path); err == nil { + return path + } + } + return "" +} + +func (c *Config) normalize() error { + if err := c.Model.normalize(); err != nil { + return errors.Wrap(err, "model") + } + + if err := c.Exec.normalize(); err != nil { + return errors.Wrap(err, "exec") + } + + if c.Resolver.IsDefined() { + if err := c.Resolver.normalize(); err != nil { + return errors.Wrap(err, "resolver") + } + } + + if c.Models == nil { + c.Models = TypeMap{} + } + + return nil +} + +func (c *Config) InjectBuiltins(s *ast.Schema) { + builtins := TypeMap{ + "__Directive": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Directive"}}, + "__DirectiveLocation": {Model: StringList{"github.com/99designs/gqlgen/graphql.String"}}, + "__Type": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Type"}}, + "__TypeKind": {Model: StringList{"github.com/99designs/gqlgen/graphql.String"}}, + "__Field": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Field"}}, + "__EnumValue": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.EnumValue"}}, + "__InputValue": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.InputValue"}}, + "__Schema": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Schema"}}, + "Float": {Model: StringList{"github.com/99designs/gqlgen/graphql.Float"}}, + "String": {Model: StringList{"github.com/99designs/gqlgen/graphql.String"}}, + "Boolean": {Model: StringList{"github.com/99designs/gqlgen/graphql.Boolean"}}, + "Int": {Model: StringList{ + "github.com/99designs/gqlgen/graphql.Int", + "github.com/99designs/gqlgen/graphql.Int32", + "github.com/99designs/gqlgen/graphql.Int64", + }}, + "ID": { + Model: StringList{ + "github.com/99designs/gqlgen/graphql.ID", + "github.com/99designs/gqlgen/graphql.IntID", + }, + }, + } + + for typeName, entry := range builtins { + if !c.Models.Exists(typeName) { + c.Models[typeName] = entry + } + } + + // These are additional types that are injected if defined in the schema as scalars. + extraBuiltins := TypeMap{ + "Time": {Model: StringList{"github.com/99designs/gqlgen/graphql.Time"}}, + "Map": {Model: StringList{"github.com/99designs/gqlgen/graphql.Map"}}, + } + + for typeName, entry := range extraBuiltins { + if t, ok := s.Types[typeName]; !c.Models.Exists(typeName) && ok && t.Kind == ast.Scalar { + c.Models[typeName] = entry + } + } +} + +func (c *Config) LoadSchema() (*ast.Schema, map[string]string, error) { + schemaStrings := map[string]string{} + + var sources []*ast.Source + + for _, filename := range c.SchemaFilename { + filename = filepath.ToSlash(filename) + var err error + var schemaRaw []byte + schemaRaw, err = ioutil.ReadFile(filename) + if err != nil { + fmt.Fprintln(os.Stderr, "unable to open schema: "+err.Error()) + os.Exit(1) + } + schemaStrings[filename] = string(schemaRaw) + sources = append(sources, &ast.Source{Name: filename, Input: schemaStrings[filename]}) + } + + schema, err := gqlparser.LoadSchema(sources...) + if err != nil { + return nil, nil, err + } + return schema, schemaStrings, nil +} + +func abs(path string) string { + absPath, err := filepath.Abs(path) + if err != nil { + panic(err) + } + return filepath.ToSlash(absPath) +} |