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

Expose the tree of field subselections to the resolver on demand #169

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ When using `UseFieldResolvers` schema option, a struct field will be used *only*
- a struct field does not implement an interface method
- a struct field does not have arguments

The method has up to two arguments:
The method has up to three arguments:

- Optional `context.Context` argument.
- Mandatory `*struct { ... }` argument if the corresponding GraphQL field has arguments. The names of the struct fields have to be [exported](https://golang.org/ref/spec#Exported_identifiers) and have to match the names of the GraphQL arguments in a non-case-sensitive way.
- Optional `[]selected.SelectedField` argument to receive the tree of selected subfields in the GraphQL query (useful for preloading of database relations)
Copy link
Member

Choose a reason for hiding this comment

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

I don't like the repetition of the work "selected" in: selected.SelectedField. It'd be nice if we can avoid that.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe selected.Field 🤔

Copy link
Member

Choose a reason for hiding this comment

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

Ideally, even better package name...

Choose a reason for hiding this comment

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

Just came across this PR. Given the author has not replied, I'm happy to do these changes (along with tests) if there's any chance that this gets approved and merged afterwards cc/ @pavelnikolov

Copy link
Member

Choose a reason for hiding this comment

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

@jesushernandez this is the most requested feature and I really want it to get some traction but I'm not sure that this is the best approach.

Choose a reason for hiding this comment

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

How can I help getting more traction?


The method has up to two results:

Expand Down
22 changes: 22 additions & 0 deletions internal/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/graph-gophers/graphql-go/internal/query"
"github.com/graph-gophers/graphql-go/internal/schema"
"github.com/graph-gophers/graphql-go/log"
pubselected "github.com/graph-gophers/graphql-go/selected"
"github.com/graph-gophers/graphql-go/trace"
)

Expand Down Expand Up @@ -160,6 +161,24 @@ func typeOf(tf *selected.TypenameField, resolver reflect.Value) string {
return ""
}

func selectionToSelectedFields(sels []selected.Selection) []pubselected.SelectedField {
var selectedFields []pubselected.SelectedField
selsLen := len(sels)
if selsLen != 0 {
selectedFields = make([]pubselected.SelectedField, 0, selsLen)
for _, sel := range sels {
selField, ok := sel.(*selected.SchemaField)
if ok {
selectedFields = append(selectedFields, pubselected.SelectedField{
Name: selField.Field.Name,
Selected: selectionToSelectedFields(selField.Sels),
})
}
}
}
return selectedFields
}

func execFieldSelection(ctx context.Context, r *Request, f *fieldToExec, path *pathSegment, applyLimiter bool) {
if applyLimiter {
r.Limiter <- struct{}{}
Expand Down Expand Up @@ -200,6 +219,9 @@ func execFieldSelection(ctx context.Context, r *Request, f *fieldToExec, path *p
if f.field.ArgsPacker != nil {
in = append(in, f.field.PackedArgs)
}
if f.field.HasSelected {
in = append(in, reflect.ValueOf(selectionToSelectedFields(f.sels)))
}
callOut := res.Method(f.field.MethodIndex).Call(in)
result = callOut[0]
if f.field.HasError && !callOut[1].IsNil() {
Expand Down
10 changes: 10 additions & 0 deletions internal/exec/resolvable/resolvable.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/graph-gophers/graphql-go/internal/common"
"github.com/graph-gophers/graphql-go/internal/exec/packer"
"github.com/graph-gophers/graphql-go/internal/schema"
pubselected "github.com/graph-gophers/graphql-go/selected"
Copy link
Member

Choose a reason for hiding this comment

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

What does the pub prefix stand for?

)

type Schema struct {
Expand Down Expand Up @@ -36,6 +37,7 @@ type Field struct {
FieldIndex int
HasContext bool
HasError bool
HasSelected bool
ArgsPacker *packer.StructPacker
ValueExec Resolvable
TraceLabel string
Expand Down Expand Up @@ -281,6 +283,7 @@ func (b *execBuilder) makeObjectExec(typeName string, fields schema.FieldList, p
}

var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
var selectedType = reflect.TypeOf([]pubselected.SelectedField(nil))
var errorType = reflect.TypeOf((*error)(nil)).Elem()

func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.Method, sf reflect.StructField,
Expand All @@ -289,6 +292,7 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.
var argsPacker *packer.StructPacker
var hasError bool
var hasContext bool
var hasSelected bool

// Validate resolver method only when there is one
if methodIndex != -1 {
Expand Down Expand Up @@ -317,6 +321,11 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.
in = in[1:]
}

hasSelected = len(in) > 0 && in[0] == selectedType
if hasSelected {
in = in[1:]
}

if len(in) > 0 {
return nil, fmt.Errorf("too many parameters")
}
Expand Down Expand Up @@ -344,6 +353,7 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.
MethodIndex: methodIndex,
FieldIndex: fieldIndex,
HasContext: hasContext,
HasSelected: hasSelected,
ArgsPacker: argsPacker,
HasError: hasError,
TraceLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name),
Expand Down
6 changes: 6 additions & 0 deletions selected/selected.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package selected

type SelectedField struct {
pavelnikolov marked this conversation as resolved.
Show resolved Hide resolved
Name string
Selected []SelectedField
}