Skip to content

Commit

Permalink
resolvergen: use the resolver type as base name for dependent types
Browse files Browse the repository at this point in the history
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")
 }
```
  • Loading branch information
fgallina committed Jun 15, 2019
1 parent 694f90a commit c5acbea
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 3 deletions.
1 change: 1 addition & 0 deletions plugin/resolvergen/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions plugin/resolvergen/resolver.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -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 -}}
Expand Down
163 changes: 163 additions & 0 deletions plugin/resolvergen/resolver_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}

0 comments on commit c5acbea

Please sign in to comment.