From c5acbead96b3c2c307809b8dd37af9dda610a84b Mon Sep 17 00:00:00 2001 From: Fabian Ezequiel Gallina Date: Sun, 26 May 2019 20:06:12 -0300 Subject: [PATCH] resolvergen: use the resolver type as base name for dependent types The template was outputing invalid code since the resolver type was not used in places like the embedding at {query,mutation}Resolver. This change also ensures that objects like {query,mutation}Resolver also use the user provided type name as suffix. Here's the resulting diff on the code generation with `type: GeneratedResolver` in the resolver config: ``` diff -u resolver.go resolvernew.go --- resolver.go 2019-05-26 20:04:15.361969755 -0300 +++ resolvernew.go 2019-05-26 20:04:54.170737786 -0300 @@ -7,20 +7,20 @@ type GeneratedResolver struct{} func (r *GeneratedResolver) Mutation() MutationResolver { - return &mutationResolver{r} + return &mutationGeneratedResolver{r} } func (r *GeneratedResolver) Query() QueryResolver { - return &queryResolver{r} + return &queryGeneratedResolver{r} } -type mutationResolver struct{ *Resolver } +type mutationGeneratedResolver struct{ *GeneratedResolver } -func (r *mutationResolver) CreateTodo(ctx context.Context, input NewTodo) (*Todo, error) { +func (r *mutationGeneratedResolver) CreateTodo(ctx context.Context, input NewTodo) (*Todo, error) { panic("not implemented") } -type queryResolver struct{ *Resolver } +type queryGeneratedResolver struct{ *GeneratedResolver } -func (r *queryResolver) Todos(ctx context.Context) ([]*Todo, error) { +func (r *queryGeneratedResolver) Todos(ctx context.Context) ([]*Todo, error) { panic("not implemented") } ``` --- plugin/resolvergen/resolver.go | 1 + plugin/resolvergen/resolver.gotpl | 6 +- plugin/resolvergen/resolver_test.go | 163 ++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 plugin/resolvergen/resolver_test.go diff --git a/plugin/resolvergen/resolver.go b/plugin/resolvergen/resolver.go index 00a6d5c9d4..6785c77c45 100644 --- a/plugin/resolvergen/resolver.go +++ b/plugin/resolvergen/resolver.go @@ -19,6 +19,7 @@ type Plugin struct{} var _ plugin.CodeGenerator = &Plugin{} func (m *Plugin) Name() string { + // TODO: typo, should be resolvergen return "resovlergen" } func (m *Plugin) GenerateCode(data *codegen.Data) error { diff --git a/plugin/resolvergen/resolver.gotpl b/plugin/resolvergen/resolver.gotpl index 7d95e6903c..66d6efac25 100644 --- a/plugin/resolvergen/resolver.gotpl +++ b/plugin/resolvergen/resolver.gotpl @@ -20,18 +20,18 @@ type {{.ResolverType}} struct {} {{ range $object := .Objects -}} {{- if $object.HasResolvers -}} func (r *{{$.ResolverType}}) {{$object.Name}}() {{ $object.ResolverInterface | ref }} { - return &{{lcFirst $object.Name}}Resolver{r} + return &{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}{r} } {{ end -}} {{ end }} {{ range $object := .Objects -}} {{- if $object.HasResolvers -}} - type {{lcFirst $object.Name}}Resolver struct { *Resolver } + type {{lcFirst $object.Name}}{{ucFirst $.ResolverType}} struct { *{{$.ResolverType}} } {{ range $field := $object.Fields -}} {{- if $field.IsResolver -}} - func (r *{{lcFirst $object.Name}}Resolver) {{$field.GoFieldName}}{{ $field.ShortResolverDeclaration }} { + func (r *{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}) {{$field.GoFieldName}}{{ $field.ShortResolverDeclaration }} { panic("not implemented") } {{ end -}} diff --git a/plugin/resolvergen/resolver_test.go b/plugin/resolvergen/resolver_test.go new file mode 100644 index 0000000000..b100f7cc74 --- /dev/null +++ b/plugin/resolvergen/resolver_test.go @@ -0,0 +1,163 @@ +package resolvergen + +import ( + "fmt" + "go/types" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + "unicode" + + "github.com/99designs/gqlgen/codegen" + "github.com/99designs/gqlgen/codegen/config" + "github.com/vektah/gqlparser/ast" +) + +func TestPlugin_Name(t *testing.T) { + t.Run("test plugin name", func(t *testing.T) { + m := &Plugin{} + if got, want := m.Name(), "resovlergen"; got != want { + t.Errorf("Plugin.Name() = %v, want %v", got, want) + } + }) +} + +// Types for testing code generation, both Mutation and +// MutationResolver must implement types.Type. +type Mutation struct{} + +func (m *Mutation) Underlying() types.Type { + return m +} + +func (m *Mutation) String() string { + return "Mutation" +} + +type MutationResolver struct{} + +func (m *MutationResolver) Underlying() types.Type { + return m +} + +func (m *MutationResolver) String() string { + return "MutationResolver" +} + +func TestPlugin_GenerateCode(t *testing.T) { + makeData := func(cfg config.PackageConfig) *codegen.Data { + m := &Mutation{} + obj := &codegen.Object{ + Definition: &ast.Definition{ + Name: fmt.Sprint(m), + }, + Root: true, + Fields: []*codegen.Field{ + &codegen.Field{ + IsResolver: true, + GoFieldName: "Name", + TypeReference: &config.TypeReference{ + GO: m, + }, + }, + }, + ResolverInterface: &MutationResolver{}, + } + obj.Fields[0].Object = obj + return &codegen.Data{ + Config: &config.Config{ + Resolver: cfg, + }, + Objects: codegen.Objects{obj}, + } + } + + t.Run("renders expected contents", func(t *testing.T) { + m := &Plugin{} + + // use a temp dir to ensure generated file uniqueness, + // since if a file already exists it won't be + // overwritten. + tempDir, err := ioutil.TempDir("", "resolvergen-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + filename := filepath.Join(tempDir, "generated.go") + + data := makeData(config.PackageConfig{ + Filename: filename, + Package: "customresolver", + Type: "CustomResolverType", + }) + if err := m.GenerateCode(data); err != nil { + t.Fatal(err) + } + + byteContents, err := ioutil.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + contents := string(byteContents) + + want := "package customresolver" + if !strings.Contains(contents, want) { + t.Fatalf("expected package name not found: want = %q\n%s", want, contents) + } + + // Skip all white-space chars after start and want + // length. Useful to jump to next non-white character + // contents for generated code assertions. + skipWhitespace := func(start int, want string) int { + return start + len(want) + strings.IndexFunc( + string(contents[start+len(want):]), + func(r rune) bool { return !unicode.IsSpace(r) }, + ) + } + // Check if want begins at the given start point. + lookingAt := func(start int, want string) bool { + return strings.Index(string(contents[start:]), want) == 0 + } + + // Assert Mutation method contents for *CustomResolverType + want = "func (r *CustomResolverType) Mutation() MutationResolver {" + start := strings.Index(contents, want) + if start == -1 { + t.Fatalf("mutation method for custom resolver not found: want = %q\n%s", want, contents) + } + start = skipWhitespace(start, want) + want = "return &mutationCustomResolverType{r}" + if !lookingAt(start, want) { + t.Fatalf("unexpected return on mutation method for custom resolver: want = %q\n%s", want, contents) + } + start = skipWhitespace(start, want) + want = "}" + if !lookingAt(start, want) { + t.Fatalf("unexpected contents on mutation method for custom resolver: want = %q\n%s", want, contents) + } + + want = "type mutationCustomResolverType struct{ *CustomResolverType }" + if !strings.Contains(contents, want) { + t.Fatalf("expected embedded resolver type struct not found: want = %q\n%s", want, contents) + } + + // Assert Name method contents for *mutationCustomResolverType + want = "func (r *mutationCustomResolverType) Name(ctx context.Context) (Mutation, error) {" + start = strings.Index(contents, want) + if start == -1 { + t.Fatalf("Name method for mutation custom resolver type not found: want = %q\n%s", want, contents) + } + start = skipWhitespace(start, want) + want = `panic("not implemented")` + if !lookingAt(start, want) { + t.Fatalf("unexpected Name method contents for mutation custom resolver type: want = %q\n%s", want, contents) + } + start = skipWhitespace(start, want) + want = "}" + if !lookingAt(start, want) { + t.Fatalf("unexpected Name method contents for mutation custom resolver type: want = %q\n%s", want, contents) + } + }) +}