diff --git a/api/generate_test.go b/api/generate_test.go new file mode 100644 index 00000000000..b23d981f127 --- /dev/null +++ b/api/generate_test.go @@ -0,0 +1,52 @@ +package api + +import ( + "os" + "path" + "testing" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/stretchr/testify/require" +) + +func cleanup(workDir string) { + _ = os.Remove(path.Join(workDir, "server.go")) + _ = os.RemoveAll(path.Join(workDir, "graph", "generated")) + _ = os.Remove(path.Join(workDir, "graph", "resolver.go")) + _ = os.Remove(path.Join(workDir, "graph", "schema.resolvers.go")) + _ = os.Remove(path.Join(workDir, "graph", "model", "models_gen.go")) +} + +func TestGenerate(t *testing.T) { + wd, _ := os.Getwd() + type args struct { + workDir string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "default", + args: args{ + workDir: path.Join(wd, "testdata", "default"), + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + cleanup(tt.args.workDir) + _ = os.Chdir(wd) + }() + _ = os.Chdir(tt.args.workDir) + cfg, err := config.LoadConfigFromDefaultLocations() + require.Nil(t, err, "failed to load config") + if err := Generate(cfg); (err != nil) != tt.wantErr { + t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/api/testdata/default/gqlgen.yml b/api/testdata/default/gqlgen.yml new file mode 100644 index 00000000000..56f2239b960 --- /dev/null +++ b/api/testdata/default/gqlgen.yml @@ -0,0 +1,56 @@ +# Where are all the schema files located? globs are supported eg src/**/*.graphqls +schema: + - graph/*.graphqls + +# Where should the generated server code go? +exec: + filename: graph/generated/generated.go + package: generated + +# Uncomment to enable federation +# federation: +# filename: graph/generated/federation.go +# package: generated + +# Where should any generated models go? +model: + filename: graph/model/models_gen.go + package: model + +# Where should the resolver implementations go? +resolver: + layout: follow-schema + dir: graph + package: graph + +# Optional: turn on use `gqlgen:"fieldName"` tags in your models +# struct_tag: json + +# Optional: turn on to use []Thing instead of []*Thing +# omit_slice_element_pointers: false + +# Optional: set to speed up generation time by not performing a final validation pass. +# skip_validation: true + +# gqlgen will search for any type names in the schema in these go packages +# if they match it will use them, otherwise it will generate them. +autobind: + - "github.com/99designs/gqlgen/api/testdata/default/graph/model" + +# This section declares type mapping between the GraphQL and go type systems +# +# The first line in each type will be used as defaults for resolver arguments and +# modelgen, the others will be allowed when binding to fields. Configure them to +# your liking +models: + ID: + model: + - github.com/99designs/gqlgen/graphql.ID + - github.com/99designs/gqlgen/graphql.Int + - github.com/99designs/gqlgen/graphql.Int64 + - github.com/99designs/gqlgen/graphql.Int32 + Int: + model: + - github.com/99designs/gqlgen/graphql.Int + - github.com/99designs/gqlgen/graphql.Int64 + - github.com/99designs/gqlgen/graphql.Int32 diff --git a/api/testdata/default/graph/model/doc.go b/api/testdata/default/graph/model/doc.go new file mode 100644 index 00000000000..8b537907051 --- /dev/null +++ b/api/testdata/default/graph/model/doc.go @@ -0,0 +1 @@ +package model diff --git a/api/testdata/default/graph/schema.graphqls b/api/testdata/default/graph/schema.graphqls new file mode 100644 index 00000000000..c6a91bb4808 --- /dev/null +++ b/api/testdata/default/graph/schema.graphqls @@ -0,0 +1,28 @@ +# GraphQL schema example +# +# https://gqlgen.com/getting-started/ + +type Todo { + id: ID! + text: String! + done: Boolean! + user: User! +} + +type User { + id: ID! + name: String! +} + +type Query { + todos: [Todo!]! +} + +input NewTodo { + text: String! + userId: String! +} + +type Mutation { + createTodo(input: NewTodo!): Todo! +} diff --git a/cmd/init_test.go b/cmd/init_test.go new file mode 100644 index 00000000000..df14d495b8d --- /dev/null +++ b/cmd/init_test.go @@ -0,0 +1,42 @@ +package cmd + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" +) + +func cleanupGenerate() { + // remove generated files + _ = os.Remove("gqlgen.yml") + _ = os.Remove("server.go") + _ = os.RemoveAll("graph") +} + +func TestInitCmd(t *testing.T) { + // setup test dir + wd, _ := os.Getwd() + testpath := path.Join(wd, "testdata", "init") + defer func() { + // remove generated files + cleanupGenerate() + _ = os.Chdir(wd) + }() + _ = os.Chdir(testpath) + + // Should ok if dir is empty + app := cli.NewApp() + app.Commands = []*cli.Command{initCmd} + args := os.Args[0:1] + args = append(args, "init") + err := app.Run(args) + require.Nil(t, err) + + // Should fail if dir is not empty, e.g. gqlgen.yml exists + err = app.Run(args) + require.NotNil(t, err) + require.Equal(t, "gqlgen.yml already exists", err.Error()) +} diff --git a/cmd/testdata/init/.keep b/cmd/testdata/init/.keep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/codegen/config/config.go b/codegen/config/config.go index 77d92bdff5f..c93861346e9 100644 --- a/codegen/config/config.go +++ b/codegen/config/config.go @@ -531,7 +531,7 @@ func (c *Config) autobind() error { } for i, p := range ps { - if p == nil { + if p == nil || p.Module == nil { return fmt.Errorf("unable to load %s - make sure you're using an import path to a package that exists", c.AutoBind[i]) } if t := p.Types.Scope().Lookup(t.Name); t != nil { diff --git a/codegen/config/config_test.go b/codegen/config/config_test.go index 7b52715861f..29f37250a7d 100644 --- a/codegen/config/config_test.go +++ b/codegen/config/config_test.go @@ -193,4 +193,21 @@ func TestAutobinding(t *testing.T) { require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/scalars/model.Banned", cfg.Models["Banned"].Model[0]) require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat.Message", cfg.Models["Message"].Model[0]) }) + + t.Run("with file path", func(t *testing.T) { + cfg := Config{ + Models: TypeMap{}, + AutoBind: []string{ + "../chat", + }, + Packages: &code.Packages{}, + } + + cfg.Schema = gqlparser.MustLoadSchema(&ast.Source{Name: "TestAutobinding.schema", Input: ` + scalar Banned + type Message { id: ID } + `}) + + require.EqualError(t, cfg.autobind(), "unable to load ../chat - make sure you're using an import path to a package that exists") + }) } diff --git a/internal/code/packages.go b/internal/code/packages.go index b6221354d3a..3ad3553ce61 100644 --- a/internal/code/packages.go +++ b/internal/code/packages.go @@ -37,6 +37,15 @@ func (p *Packages) ReloadAll(importPaths ...string) []*packages.Package { return p.LoadAll(importPaths...) } +func (p *Packages) checkModuleLoaded(pkgs []*packages.Package) bool { + for i := range pkgs { + if pkgs[i] == nil || pkgs[i].Module == nil { + return false + } + } + return true +} + // LoadAll will call packages.Load and return the package data for the given packages, // but if the package already have been loaded it will return cached values instead. func (p *Packages) LoadAll(importPaths ...string) []*packages.Package { @@ -55,6 +64,13 @@ func (p *Packages) LoadAll(importPaths ...string) []*packages.Package { if len(missing) > 0 { p.numLoadCalls++ pkgs, err := packages.Load(&packages.Config{Mode: mode}, missing...) + + // Sometimes packages.Load not loaded the module info. Call it again to reload it. + if !p.checkModuleLoaded(pkgs) { + fmt.Println("reloading module info") + pkgs, err = packages.Load(&packages.Config{Mode: mode}, missing...) + } + if err != nil { p.loadErrors = append(p.loadErrors, err) }