Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resolvergen: use the resolver type as base name for dependent types #728

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆

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{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe an easier and simpler way to write this test would be to use golden files (https://medium.com/@jarifibrahim/golden-files-why-you-should-use-them-47087ec994bf)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already do this using code review of the committed generated files. No need for update, just commit the generated code and push.

All this test should need to do is call the generator with some config, eg. It may not look like its asserting anything, but really its output is tested in three ways:

  • firstly we run goimports when saving go files, so running generate should error out in the test if it generates invalid code.
  • if that fails the linter should see invalid code, and fail the branch.
  • code review catches changes. diffs are readable in github.


// 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)
}
})
}