Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into handler-refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
vektah committed Nov 12, 2019
2 parents 572fb41 + 9cfd817 commit dc76d02
Show file tree
Hide file tree
Showing 26 changed files with 1,563 additions and 445 deletions.
7 changes: 6 additions & 1 deletion codegen/config/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,12 @@ func (t *TypeReference) UniquenessKey() string {
nullability = "N"
}

return nullability + t.Definition.Name + "2" + templates.TypeIdentifier(t.GO)
var elemNullability = ""
if t.GQL.Elem != nil && t.GQL.Elem.NonNull {
// Fix for #896
elemNullability = "ᚄ"
}
return nullability + t.Definition.Name + "2" + templates.TypeIdentifier(t.GO) + elemNullability
}

func (t *TypeReference) MarshalFunc() string {
Expand Down
233 changes: 170 additions & 63 deletions codegen/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,75 +184,168 @@ func (b *builder) bindField(obj *Object, f *Field) (errret error) {
}
}

// findField attempts to match the name to a struct field with the following
// priorites:
// 1. Any method with a matching name
// 2. Any Fields with a struct tag (see config.StructTag)
// 3. Any fields with a matching name
// 4. Same logic again for embedded fields
func (b *builder) findBindTarget(named *types.Named, name string) (types.Object, error) {
for i := 0; i < named.NumMethods(); i++ {
method := named.Method(i)
if !method.Exported() {
continue
func (b *builder) findBindTarget(in types.Type, name string) (types.Object, error) {
switch t := in.(type) {
case *types.Named:
if _, ok := t.Underlying().(*types.Interface); ok {
return nil, errors.New("can't bind to an interface at root")
}
case *types.Interface:
return nil, errors.New("can't bind to an interface at root")
}

if !strings.EqualFold(method.Name(), name) {
continue
}
return b.findBindTargetRecur(in, name)
}

// findBindTargetRecur attempts to match the name to a field or method on a Type
// with the following priorites:
// 1. Any Fields with a struct tag (see config.StructTag). Errors if more than one match is found
// 2. Any method or field with a matching name. Errors if more than one match is found
// 3. Same logic again for embedded fields
func (b *builder) findBindTargetRecur(t types.Type, name string) (types.Object, error) {
// NOTE: a struct tag will override both methods and fields
// Bind to struct tag
found, err := b.findBindStructTagTarget(t, name)
if found != nil || err != nil {
return found, err
}

// Search for a method to bind to
foundMethod, err := b.findBindMethodTarget(t, name)
if err != nil {
return nil, err
}

return method, nil
// Search for a field to bind to
foundField, err := b.findBindFieldTarget(t, name)
if err != nil {
return nil, err
}

strukt, ok := named.Underlying().(*types.Struct)
if !ok {
return nil, fmt.Errorf("not a struct")
switch {
case foundField == nil && foundMethod != nil:
// Bind to method
return foundMethod, nil
case foundField != nil && foundMethod == nil:
// Bind to field
return foundField, nil
case foundField != nil && foundMethod != nil:
// Error
return nil, errors.Errorf("found more than one way to bind for %s", name)
}
return b.findBindStructTarget(strukt, name)

// Search embeds
return b.findBindEmbedsTarget(t, name)
}

func (b *builder) findBindStructTarget(strukt *types.Struct, name string) (types.Object, error) {
// struct tags have the highest priority
if b.Config.StructTag != "" {
var foundField *types.Var
for i := 0; i < strukt.NumFields(); i++ {
field := strukt.Field(i)
if !field.Exported() {
func (b *builder) findBindStructTagTarget(in types.Type, name string) (types.Object, error) {
if b.Config.StructTag == "" {
return nil, nil
}

switch t := in.(type) {
case *types.Named:
return b.findBindStructTagTarget(t.Underlying(), name)
case *types.Struct:
var found types.Object
for i := 0; i < t.NumFields(); i++ {
field := t.Field(i)
if !field.Exported() || field.Embedded() {
continue
}
tags := reflect.StructTag(strukt.Tag(i))
tags := reflect.StructTag(t.Tag(i))
if val, ok := tags.Lookup(b.Config.StructTag); ok && equalFieldName(val, name) {
if foundField != nil {
if found != nil {
return nil, errors.Errorf("tag %s is ambigious; multiple fields have the same tag value of %s", b.Config.StructTag, val)
}

foundField = field
found = field
}
}
if foundField != nil {
return foundField, nil

return found, nil
}

return nil, nil
}

func (b *builder) findBindMethodTarget(in types.Type, name string) (types.Object, error) {
switch t := in.(type) {
case *types.Named:
if _, ok := t.Underlying().(*types.Interface); ok {
return b.findBindMethodTarget(t.Underlying(), name)
}

return b.findBindMethoderTarget(t.Method, t.NumMethods(), name)
case *types.Interface:
// FIX-ME: Should use ExplicitMethod here? What's the difference?
return b.findBindMethoderTarget(t.Method, t.NumMethods(), name)
}

// Then matching field names
for i := 0; i < strukt.NumFields(); i++ {
field := strukt.Field(i)
if !field.Exported() {
return nil, nil
}

func (b *builder) findBindMethoderTarget(methodFunc func(i int) *types.Func, methodCount int, name string) (types.Object, error) {
var found types.Object
for i := 0; i < methodCount; i++ {
method := methodFunc(i)
if !method.Exported() || !strings.EqualFold(method.Name(), name) {
continue
}
if equalFieldName(field.Name(), name) { // aqui!
return field, nil

if found != nil {
return nil, errors.Errorf("found more than one matching method to bind for %s", name)
}

found = method
}

// Then look in embedded structs
for i := 0; i < strukt.NumFields(); i++ {
field := strukt.Field(i)
if !field.Exported() {
continue
return found, nil
}

func (b *builder) findBindFieldTarget(in types.Type, name string) (types.Object, error) {
switch t := in.(type) {
case *types.Named:
return b.findBindFieldTarget(t.Underlying(), name)
case *types.Struct:
var found types.Object
for i := 0; i < t.NumFields(); i++ {
field := t.Field(i)
if !field.Exported() || !equalFieldName(field.Name(), name) {
continue
}

if found != nil {
return nil, errors.Errorf("found more than one matching field to bind for %s", name)
}

found = field
}

if !field.Anonymous() {
return found, nil
}

return nil, nil
}

func (b *builder) findBindEmbedsTarget(in types.Type, name string) (types.Object, error) {
switch t := in.(type) {
case *types.Named:
return b.findBindEmbedsTarget(t.Underlying(), name)
case *types.Struct:
return b.findBindStructEmbedsTarget(t, name)
case *types.Interface:
return b.findBindInterfaceEmbedsTarget(t, name)
}

return nil, nil
}

func (b *builder) findBindStructEmbedsTarget(strukt *types.Struct, name string) (types.Object, error) {
var found types.Object
for i := 0; i < strukt.NumFields(); i++ {
field := strukt.Field(i)
if !field.Embedded() {
continue
}

Expand All @@ -261,29 +354,43 @@ func (b *builder) findBindStructTarget(strukt *types.Struct, name string) (types
fieldType = ptr.Elem()
}

switch fieldType := fieldType.(type) {
case *types.Named:
f, err := b.findBindTarget(fieldType, name)
if err != nil {
return nil, err
}
if f != nil {
return f, nil
}
case *types.Struct:
f, err := b.findBindStructTarget(fieldType, name)
if err != nil {
return nil, err
}
if f != nil {
return f, nil
}
default:
panic(fmt.Errorf("unknown embedded field type %T", field.Type()))
f, err := b.findBindTargetRecur(fieldType, name)
if err != nil {
return nil, err
}

if f != nil && found != nil {
return nil, errors.Errorf("found more than one way to bind for %s", name)
}

if f != nil {
found = f
}
}

return nil, nil
return found, nil
}

func (b *builder) findBindInterfaceEmbedsTarget(iface *types.Interface, name string) (types.Object, error) {
var found types.Object
for i := 0; i < iface.NumEmbeddeds(); i++ {
embeddedType := iface.EmbeddedType(i)

f, err := b.findBindTargetRecur(embeddedType, name)
if err != nil {
return nil, err
}

if f != nil && found != nil {
return nil, errors.Errorf("found more than one way to bind for %s", name)
}

if f != nil {
found = f
}
}

return found, nil
}

func (f *Field) HasDirectives() bool {
Expand Down
18 changes: 9 additions & 9 deletions codegen/field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ type Embed struct {
scope, err := parseScope(input, "test")
require.NoError(t, err)

std := scope.Lookup("Std").Type().Underlying().(*types.Struct)
anon := scope.Lookup("Anon").Type().Underlying().(*types.Struct)
tags := scope.Lookup("Tags").Type().Underlying().(*types.Struct)
amb := scope.Lookup("Amb").Type().Underlying().(*types.Struct)
embed := scope.Lookup("Embed").Type().Underlying().(*types.Struct)
std := scope.Lookup("Std").Type().(*types.Named)
anon := scope.Lookup("Anon").Type().(*types.Named)
tags := scope.Lookup("Tags").Type().(*types.Named)
amb := scope.Lookup("Amb").Type().(*types.Named)
embed := scope.Lookup("Embed").Type().(*types.Named)

tests := []struct {
Name string
Struct *types.Struct
Named *types.Named
Field string
Tag string
Expected string
Expand All @@ -65,13 +65,13 @@ type Embed struct {

for _, tt := range tests {
b := builder{Config: &config.Config{StructTag: tt.Tag}}
field, err := b.findBindStructTarget(tt.Struct, tt.Field)
target, err := b.findBindTarget(tt.Named, tt.Field)
if tt.ShouldError {
require.Nil(t, field, tt.Name)
require.Nil(t, target, tt.Name)
require.Error(t, err, tt.Name)
} else {
require.NoError(t, err, tt.Name)
require.Equal(t, tt.Expected, field.Name(), tt.Name)
require.Equal(t, tt.Expected, target.Name(), tt.Name)
}
}
}
Expand Down
43 changes: 43 additions & 0 deletions codegen/testserver/embedded.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package testserver

// EmbeddedCase1 model
type EmbeddedCase1 struct {
Empty
*ExportedEmbeddedPointerAfterInterface
}

// Empty interface
type Empty interface{}

// ExportedEmbeddedPointerAfterInterface model
type ExportedEmbeddedPointerAfterInterface struct{}

// ExportedEmbeddedPointerExportedMethod method
func (*ExportedEmbeddedPointerAfterInterface) ExportedEmbeddedPointerExportedMethod() string {
return "ExportedEmbeddedPointerExportedMethodResponse"
}

// EmbeddedCase2 model
type EmbeddedCase2 struct {
*unexportedEmbeddedPointer
}

type unexportedEmbeddedPointer struct{}

// UnexportedEmbeddedPointerExportedMethod method
func (*unexportedEmbeddedPointer) UnexportedEmbeddedPointerExportedMethod() string {
return "UnexportedEmbeddedPointerExportedMethodResponse"
}

// EmbeddedCase3 model
type EmbeddedCase3 struct {
unexportedEmbeddedInterface
}

type unexportedEmbeddedInterface interface {
nestedInterface
}

type nestedInterface interface {
UnexportedEmbeddedInterfaceExportedMethod() string
}
17 changes: 17 additions & 0 deletions codegen/testserver/embedded.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
extend type Query {
embeddedCase1: EmbeddedCase1
embeddedCase2: EmbeddedCase2
embeddedCase3: EmbeddedCase3
}

type EmbeddedCase1 @goModel(model:"testserver.EmbeddedCase1") {
exportedEmbeddedPointerExportedMethod: String!
}

type EmbeddedCase2 @goModel(model:"testserver.EmbeddedCase2") {
unexportedEmbeddedPointerExportedMethod: String!
}

type EmbeddedCase3 @goModel(model:"testserver.EmbeddedCase3") {
unexportedEmbeddedInterfaceExportedMethod: String!
}
Loading

0 comments on commit dc76d02

Please sign in to comment.