Skip to content

Commit 5472fce

Browse files
committed
Support exposing the selected fields in a resolver
Copied from graph-gophers#169 and used package 'query' instead of 'selected'.
1 parent 910c80f commit 5472fce

File tree

5 files changed

+161
-0
lines changed

5 files changed

+161
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ The method has up to two arguments:
5151

5252
- Optional `context.Context` argument.
5353
- 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.
54+
- Optional `[]query.SelectedField` argument to receive the tree of selected subfields in the GraphQL query (useful for preloading of database relations)
5455

5556
The method has up to two results:
5657

graphql_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package graphql_test
22

33
import (
44
"context"
5+
"reflect"
56
"testing"
67
"time"
78

89
"github.com/qdentity/graphql-go"
910
"github.com/qdentity/graphql-go/example/starwars"
1011
"github.com/qdentity/graphql-go/gqltesting"
12+
"github.com/qdentity/graphql-go/query"
1113
)
1214

1315
type helloWorldResolver1 struct{}
@@ -16,6 +18,26 @@ func (r *helloWorldResolver1) Hello() string {
1618
return "Hello world!"
1719
}
1820

21+
type selectedFieldsResolver struct {
22+
assert func(fields []query.SelectedField)
23+
}
24+
25+
func (r *selectedFieldsResolver) Do1(fields []query.SelectedField) *helloWorldResolver1 {
26+
r.assert(fields)
27+
return &helloWorldResolver1{}
28+
}
29+
30+
func (r *selectedFieldsResolver) Do2(_ context.Context, fields []query.SelectedField) *helloWorldResolver1 {
31+
r.assert(fields)
32+
return &helloWorldResolver1{}
33+
}
34+
35+
func (r *selectedFieldsResolver) Do3(_ context.Context, args struct{ Name string },
36+
fields []query.SelectedField) *helloWorldResolver1 {
37+
r.assert(fields)
38+
return &helloWorldResolver1{}
39+
}
40+
1941
type helloWorldResolver2 struct{}
2042

2143
func (r *helloWorldResolver2) Hello(ctx context.Context) (string, error) {
@@ -111,6 +133,107 @@ func TestHelloWorld(t *testing.T) {
111133
})
112134
}
113135

136+
func TestSelectedFields(t *testing.T) {
137+
gqltesting.RunTests(t, []*gqltesting.Test{
138+
{
139+
Schema: graphql.MustParseSchema(`
140+
schema {
141+
query: Query
142+
}
143+
type Hello {
144+
hello: String!
145+
}
146+
type Query {
147+
do1: Hello!
148+
}
149+
`, &selectedFieldsResolver{
150+
assert: func(got []query.SelectedField) {
151+
want := []query.SelectedField{
152+
{Name: "hello"},
153+
}
154+
if !reflect.DeepEqual(want, got) {
155+
t.Errorf("want %#v, got %#v", want, got)
156+
}
157+
},
158+
}),
159+
Query: `
160+
{
161+
do1 { hello }
162+
}
163+
`,
164+
ExpectedResult: `
165+
{
166+
"do1": { "hello": "Hello world!" }
167+
}
168+
`,
169+
},
170+
{
171+
Schema: graphql.MustParseSchema(`
172+
schema {
173+
query: Query
174+
}
175+
type Hello {
176+
hello: String!
177+
}
178+
type Query {
179+
do2: Hello!
180+
}
181+
`, &selectedFieldsResolver{
182+
assert: func(got []query.SelectedField) {
183+
want := []query.SelectedField{
184+
{Name: "hello"},
185+
}
186+
if !reflect.DeepEqual(want, got) {
187+
t.Errorf("want %#v, got %#v", want, got)
188+
}
189+
},
190+
}),
191+
Query: `
192+
{
193+
do2 { hello }
194+
}
195+
`,
196+
ExpectedResult: `
197+
{
198+
"do2": { "hello": "Hello world!" }
199+
}
200+
`,
201+
},
202+
{
203+
Schema: graphql.MustParseSchema(`
204+
schema {
205+
query: Query
206+
}
207+
type Hello {
208+
hello: String!
209+
}
210+
type Query {
211+
do3(name: String!): Hello!
212+
}
213+
`, &selectedFieldsResolver{
214+
assert: func(got []query.SelectedField) {
215+
want := []query.SelectedField{
216+
{Name: "hello"},
217+
}
218+
if !reflect.DeepEqual(want, got) {
219+
t.Errorf("want %#v, got %#v", want, got)
220+
}
221+
},
222+
}),
223+
Query: `
224+
{
225+
do3(name: "") { hello }
226+
}
227+
`,
228+
ExpectedResult: `
229+
{
230+
"do3": { "hello": "Hello world!" }
231+
}
232+
`,
233+
},
234+
})
235+
}
236+
114237
func TestHelloSnake(t *testing.T) {
115238
gqltesting.RunTests(t, []*gqltesting.Test{
116239
{

internal/exec/exec.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/qdentity/graphql-go/internal/query"
1515
"github.com/qdentity/graphql-go/internal/schema"
1616
"github.com/qdentity/graphql-go/log"
17+
pubquery "github.com/qdentity/graphql-go/query"
1718
"github.com/qdentity/graphql-go/trace"
1819
)
1920

@@ -142,6 +143,24 @@ func typeOf(tf *selected.TypenameField, resolver reflect.Value) string {
142143
return ""
143144
}
144145

146+
func selectionToSelectedFields(sels []selected.Selection) []pubquery.SelectedField {
147+
n := len(sels)
148+
if n == 0 {
149+
return nil
150+
}
151+
selectedFields := make([]pubquery.SelectedField, 0, n)
152+
for _, sel := range sels {
153+
selField, ok := sel.(*selected.SchemaField)
154+
if ok {
155+
selectedFields = append(selectedFields, pubquery.SelectedField{
156+
Name: selField.Field.Name,
157+
Selected: selectionToSelectedFields(selField.Sels),
158+
})
159+
}
160+
}
161+
return selectedFields
162+
}
163+
145164
func execFieldSelection(ctx context.Context, r *Request, f *fieldToExec, path *pathSegment, applyLimiter bool) {
146165
if applyLimiter {
147166
r.Limiter <- struct{}{}
@@ -180,6 +199,9 @@ func execFieldSelection(ctx context.Context, r *Request, f *fieldToExec, path *p
180199
if f.field.ArgsPacker != nil {
181200
in = append(in, f.field.PackedArgs)
182201
}
202+
if f.field.HasSelected {
203+
in = append(in, reflect.ValueOf(selectionToSelectedFields(f.sels)))
204+
}
183205
callOut := f.resolver.Method(f.field.MethodIndex).Call(in)
184206
result = callOut[0]
185207
if f.field.HasError && !callOut[1].IsNil() {

internal/exec/resolvable/resolvable.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/qdentity/graphql-go/internal/common"
1010
"github.com/qdentity/graphql-go/internal/exec/packer"
1111
"github.com/qdentity/graphql-go/internal/schema"
12+
pubquery "github.com/qdentity/graphql-go/query"
1213
)
1314

1415
type Schema struct {
@@ -34,6 +35,7 @@ type Field struct {
3435
MethodIndex int
3536
HasContext bool
3637
HasError bool
38+
HasSelected bool
3739
ArgsPacker *packer.StructPacker
3840
ValueExec Resolvable
3941
TraceLabel string
@@ -251,6 +253,7 @@ func (b *execBuilder) makeObjectExec(typeName string, fields schema.FieldList, p
251253
}
252254

253255
var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
256+
var selectedType = reflect.TypeOf([]pubquery.SelectedField(nil))
254257
var errorType = reflect.TypeOf((*error)(nil)).Elem()
255258

256259
func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.Method, methodIndex int, methodHasReceiver bool) (*Field, error) {
@@ -280,6 +283,11 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.
280283
in = in[1:]
281284
}
282285

286+
hasSelected := len(in) > 0 && in[0] == selectedType
287+
if hasSelected {
288+
in = in[1:]
289+
}
290+
283291
if len(in) > 0 {
284292
return nil, fmt.Errorf("too many parameters")
285293
}
@@ -300,6 +308,7 @@ func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.
300308
TypeName: typeName,
301309
MethodIndex: methodIndex,
302310
HasContext: hasContext,
311+
HasSelected: hasSelected,
303312
ArgsPacker: argsPacker,
304313
HasError: hasError,
305314
TraceLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name),

query/selected_field.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package query
2+
3+
type SelectedField struct {
4+
Name string
5+
Selected []SelectedField
6+
}

0 commit comments

Comments
 (0)