Skip to content

Commit

Permalink
WIP: Fix findBindTarget
Browse files Browse the repository at this point in the history
  • Loading branch information
matiasanaya committed Nov 5, 2019
1 parent 840d3f4 commit 527c55f
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 48 deletions.
144 changes: 98 additions & 46 deletions codegen/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,73 +186,109 @@ 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
// 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) findBindTarget(named *types.Named, name string) (types.Object, error) {
strukt, isStruct := named.Underlying().(*types.Struct)
if isStruct {
// NOTE: a struct tag will override both methods and fields
// Bind to struct tag
found, err := b.findBindStructTagTarget(strukt, name)
if found != nil || err != nil {
return found, err
}
}

// Search for a method to bind to
var foundMethod types.Object
for i := 0; i < named.NumMethods(); i++ {
method := named.Method(i)
if !method.Exported() {
if !method.Exported() || !strings.EqualFold(method.Name(), name) {
continue
}

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

return method, nil
}

strukt, ok := named.Underlying().(*types.Struct)
if !ok {
return nil, fmt.Errorf("not a struct")
foundMethod = method
}
return b.findBindStructTarget(strukt, 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() {
continue
}
tags := reflect.StructTag(strukt.Tag(i))
if val, ok := tags.Lookup(b.Config.StructTag); ok && equalFieldName(val, name) {
if foundField != nil {
return nil, errors.Errorf("tag %s is ambigious; multiple fields have the same tag value of %s", b.Config.StructTag, val)
}

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

switch {
case foundField == nil && foundMethod == nil:
// Search embeds
return b.findBindEmbedsTarget(strukt, name)
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)
}
}

// Then matching field names
// Bind to method or don't bind at all
return foundMethod, nil
}

func (b *builder) findBindStructTagTarget(strukt *types.Struct, name string) (types.Object, error) {
if b.Config.StructTag == "" {
return nil, nil
}

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

found = field
}
}

// Then look in embedded structs
return found, nil
}

func (b *builder) findBindFieldTarget(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.Exported() {
if !field.Exported() || !equalFieldName(field.Name(), name) {
continue
}

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

found = field
}

return found, nil
}

func (b *builder) findBindEmbedsTarget(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 @@ -267,23 +303,39 @@ func (b *builder) findBindStructTarget(strukt *types.Struct, name string) (types
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 {
return f, nil
found = f
}
case *types.Struct:
f, err := b.findBindStructTarget(fieldType, name)
f, err := b.findBindStructTagTarget(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
continue
}

f, err = b.findBindFieldTarget(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 {
return f, nil
found = f
}
default:
panic(fmt.Errorf("unknown embedded field type %T", field.Type()))
}
}

return nil, nil
return found, nil
}

func (f *Field) HasDirectives() bool {
Expand Down
4 changes: 2 additions & 2 deletions codegen/testserver/embedded_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestEmbedded(t *testing.T) {
t.Run("embedded case 1", func(t *testing.T) {
var resp struct {
Case1 struct {
ExportedEmbeddedPointerExportedMethod bool
ExportedEmbeddedPointerExportedMethod string
}
}
err := c.Post(`query { case1 { exportedEmbeddedPointerExportedMethod } }`, &resp)
Expand All @@ -45,7 +45,7 @@ func TestEmbedded(t *testing.T) {
t.Run("embedded case 2", func(t *testing.T) {
var resp struct {
Case2 struct {
UnexportedEmbeddedPointerExportedMethod bool
UnexportedEmbeddedPointerExportedMethod string
}
}
err := c.Post(`query { case2 { unexportedEmbeddedPointerExportedMethod } }`, &resp)
Expand Down

0 comments on commit 527c55f

Please sign in to comment.