diff --git a/codegen/config.go b/codegen/config.go index e1e5ceb3480..874f6e32a46 100644 --- a/codegen/config.go +++ b/codegen/config.go @@ -2,12 +2,55 @@ package codegen import ( "fmt" + "io/ioutil" + "os" "path/filepath" "github.com/pkg/errors" "github.com/vektah/gqlgen/neelance/schema" + "gopkg.in/yaml.v2" ) +var defaults = Config{ + SchemaFilename: "schema.graphql", + Model: PackageConfig{Filename: "models_gen.go"}, + Exec: PackageConfig{Filename: "generated.go"}, +} + +var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"} + +// LoadDefaultConfig 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 LoadDefaultConfig() (*Config, error) { + cfgFile, err := findCfg() + if err != nil || cfgFile == "" { + cpy := defaults + return &cpy, 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 := defaults + + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, errors.Wrap(err, "unable to read config") + } + + if err := yaml.Unmarshal(b, &config); err != nil { + return nil, errors.Wrap(err, "unable to parse config") + } + + return &config, nil +} + type Config struct { SchemaFilename string `yaml:"schema,omitempty"` SchemaStr string `yaml:"-"` @@ -75,3 +118,30 @@ func (tm TypeMap) Check() error { } return nil } + +// 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 != "/" { + dir = filepath.Dir(dir) + cfg = findCfgInDir(dir) + } + + 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 "" +} diff --git a/codegen/config_test.go b/codegen/config_test.go new file mode 100644 index 00000000000..3d9d383f101 --- /dev/null +++ b/codegen/config_test.go @@ -0,0 +1,54 @@ +package codegen + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLoadConfig(t *testing.T) { + t.Run("config does not exist", func(t *testing.T) { + _, err := LoadConfig("doesnotexist.yml") + require.EqualError(t, err, "unable to read config: open doesnotexist.yml: no such file or directory") + }) + + t.Run("malformed config", func(t *testing.T) { + _, err := LoadConfig("testdata/cfg/malformedconfig.yml") + require.EqualError(t, err, "unable to parse config: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `asdf` into codegen.Config") + }) +} + +func TestLoadDefaultConfig(t *testing.T) { + testDir, err := os.Getwd() + require.NoError(t, err) + var cfg *Config + + t.Run("will find closest match", func(t *testing.T) { + err = os.Chdir(filepath.Join(testDir, "testdata", "cfg", "subdir")) + require.NoError(t, err) + + cfg, err = LoadDefaultConfig() + require.NoError(t, err) + require.Equal(t, cfg.SchemaFilename, "inner") + }) + + t.Run("will find config in parent dirs", func(t *testing.T) { + err = os.Chdir(filepath.Join(testDir, "testdata", "cfg", "otherdir")) + require.NoError(t, err) + + cfg, err = LoadDefaultConfig() + require.NoError(t, err) + require.Equal(t, cfg.SchemaFilename, "outer") + }) + + t.Run("will fallback to defaults", func(t *testing.T) { + err = os.Chdir(testDir) + require.NoError(t, err) + + cfg, err = LoadDefaultConfig() + require.NoError(t, err) + require.Equal(t, cfg.SchemaFilename, "schema.graphql") + }) +} diff --git a/codegen/testdata/cfg/gqlgen.yml b/codegen/testdata/cfg/gqlgen.yml new file mode 100644 index 00000000000..1381aafd033 --- /dev/null +++ b/codegen/testdata/cfg/gqlgen.yml @@ -0,0 +1 @@ +schema: outer diff --git a/codegen/testdata/cfg/malformedconfig.yml b/codegen/testdata/cfg/malformedconfig.yml new file mode 100644 index 00000000000..8bd6648ed13 --- /dev/null +++ b/codegen/testdata/cfg/malformedconfig.yml @@ -0,0 +1 @@ +asdf diff --git a/codegen/testdata/cfg/otherdir/.gitkeep b/codegen/testdata/cfg/otherdir/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/codegen/testdata/cfg/subdir/gqlgen.yaml b/codegen/testdata/cfg/subdir/gqlgen.yaml new file mode 100644 index 00000000000..303effe0ac3 --- /dev/null +++ b/codegen/testdata/cfg/subdir/gqlgen.yaml @@ -0,0 +1 @@ +schema: inner diff --git a/docs/content/getting-started.md b/docs/content/getting-started.md index ae2d29f4491..6defb3ed552 100644 --- a/docs/content/getting-started.md +++ b/docs/content/getting-started.md @@ -267,7 +267,7 @@ go get github.com/vektah/gorunpkg Now at the top of our graph.go: ```go -//go:generate gorunpkg gqlgen +//go:generate gorunpkg github.com/vektah/gqlgen package graph ``` diff --git a/main.go b/main.go index e7040b60416..5884aaf3a6b 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,7 @@ import ( "gopkg.in/yaml.v2" ) -var configFilename = flag.String("config", ".gqlgen.yml", "the file to configuration to") +var configFilename = flag.String("config", "", "the file to configuration to") var output = flag.String("out", "", "the file to write to") var models = flag.String("models", "", "the file to write the models to") var schemaFilename = flag.String("schema", "", "the graphql schema to generate types from") @@ -32,7 +32,17 @@ func main() { os.Exit(1) } - config := loadConfig() + var config *codegen.Config + var err error + if *configFilename != "" { + config, err = codegen.LoadConfig(*configFilename) + } else { + config, err = codegen.LoadDefaultConfig() + } + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } // overwrite by commandline options var emitYamlGuidance bool @@ -76,7 +86,7 @@ func main() { os.Exit(1) } - fmt.Fprintf(os.Stderr, "DEPRECATION WARNING: we are moving away from the json typemap, instead create a .gqlgen.yml with the following content:\n\n%s\n", string(b)) + fmt.Fprintf(os.Stderr, "DEPRECATION WARNING: we are moving away from the json typemap, instead create a gqlgen.yml with the following content:\n\n%s\n", string(b)) } err = codegen.Generate(*config) @@ -86,29 +96,6 @@ func main() { } } -func loadConfig() *codegen.Config { - config := &codegen.Config{ - SchemaFilename: "schema.graphql", - Model: codegen.PackageConfig{Filename: "models_gen.go"}, - Exec: codegen.PackageConfig{Filename: "generated.go"}, - } - - b, err := ioutil.ReadFile(*configFilename) - if os.IsNotExist(err) { - return config - } else if err != nil { - fmt.Fprintln(os.Stderr, "unable to open config: "+err.Error()) - os.Exit(1) - } - - if err := yaml.Unmarshal(b, config); err != nil { - fmt.Fprintln(os.Stderr, "unable to parse config: "+err.Error()) - os.Exit(1) - } - - return config -} - func loadModelMap() codegen.TypeMap { var goTypes map[string]string b, err := ioutil.ReadFile(*typemap) diff --git a/test/.gqlgen.yml b/test/config.yaml similarity index 100% rename from test/.gqlgen.yml rename to test/config.yaml diff --git a/test/resolvers_test.go b/test/resolvers_test.go index 3a468fb617b..94192722306 100644 --- a/test/resolvers_test.go +++ b/test/resolvers_test.go @@ -1,4 +1,4 @@ -//go:generate gorunpkg github.com/vektah/gqlgen +//go:generate gorunpkg github.com/vektah/gqlgen -config config.yaml package test