• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

graphql-go / graphql / 1912

09 May 2026 10:10PM UTC coverage: 85.967% (-6.3%) from 92.284%
1912

Pull #740

circleci

Nthalk
perf: skip extension hooks when schema has no extensions

resolvePlannedField unconditionally called handleExtensionsResolveFieldDidStart,
which allocates a map[string]ResolveFieldFinishFunc + a closure on
every resolved field — even when the schema has zero extensions
registered. With ~1000 fields per request that's 2000 wasted allocs
per request on the common no-extensions case.

Gate the call (and the matching finish-handler invocation) behind
`len(eCtx.Schema.extensions) > 0`.

Wide-query bench (100 fields × 10 items):
  before: 1.15ms / 9043 allocs/op
  after:  0.99ms / 7040 allocs/op   (-14% time, -22% allocs)

Hot-loop with native variables:
  before: 1.44ms / 9888 allocs/op
  after:  1.30ms / 7884 allocs/op   (-10% time, -20% allocs)

Schemas with extensions registered take the same path as before.
Pull Request #740: Plan + PlanQuery + ExecutePlan + PlanCache (cacheable execution shape)

916 of 1070 new or added lines in 4 files covered. (85.61%)

97 existing lines in 1 file now uncovered.

8350 of 9713 relevant lines covered (85.97%)

1296.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.42
/executor.go
1
package graphql
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "reflect"
8
        "strings"
9

10
        "github.com/graphql-go/graphql/gqlerrors"
11
        "github.com/graphql-go/graphql/language/ast"
12
)
13

14
type ExecuteParams struct {
15
        Schema        Schema
16
        Root          interface{}
17
        AST           *ast.Document
18
        OperationName string
19
        Args          map[string]interface{}
20

21
        // Context may be provided to pass application-specific per-request
22
        // information to resolve functions.
23
        Context context.Context
24
}
25

26
// Execute runs an operation against a schema. Behavior is unchanged
27
// from prior releases: it now plans + executes via PlanQuery /
28
// ExecutePlan internally, but builds a fresh plan per call. Callers
29
// that issue the same query repeatedly should hold onto the *Plan
30
// returned by PlanQuery and pass it to ExecutePlan to skip the
31
// per-call planning work.
32
func Execute(p ExecuteParams) (result *Result) {
239✔
33
        plan, err := PlanQuery(&p.Schema, p.AST, p.OperationName)
239✔
34
        if err != nil {
245✔
35
                return &Result{Errors: gqlerrors.FormatErrors(err)}
6✔
36
        }
6✔
37
        return ExecutePlan(plan, p)
233✔
38
}
39

40
type buildExecutionCtxParams struct {
41
        Schema        Schema
42
        Root          interface{}
43
        AST           *ast.Document
44
        OperationName string
45
        Args          map[string]interface{}
46
        Result        *Result
47
        Context       context.Context
48
}
49

50
type executionContext struct {
51
        Schema         Schema
52
        Fragments      map[string]ast.Definition
53
        Root           interface{}
54
        Operation      ast.Definition
55
        VariableValues map[string]interface{}
56
        Errors         []gqlerrors.FormattedError
57
        Context        context.Context
58
}
59

60
func buildExecutionContext(p buildExecutionCtxParams) (*executionContext, error) {
7✔
61
        eCtx := &executionContext{}
7✔
62
        var operation *ast.OperationDefinition
7✔
63
        fragments := map[string]ast.Definition{}
7✔
64

7✔
65
        for _, definition := range p.AST.Definitions {
14✔
66
                switch definition := definition.(type) {
7✔
67
                case *ast.OperationDefinition:
7✔
68
                        if (p.OperationName == "") && operation != nil {
7✔
UNCOV
69
                                return nil, errors.New("Must provide operation name if query contains multiple operations.")
×
UNCOV
70
                        }
×
71
                        if p.OperationName == "" || definition.GetName() != nil && definition.GetName().Value == p.OperationName {
14✔
72
                                operation = definition
7✔
73
                        }
7✔
UNCOV
74
                case *ast.FragmentDefinition:
×
UNCOV
75
                        key := ""
×
UNCOV
76
                        if definition.GetName() != nil && definition.GetName().Value != "" {
×
UNCOV
77
                                key = definition.GetName().Value
×
UNCOV
78
                        }
×
UNCOV
79
                        fragments[key] = definition
×
UNCOV
80
                default:
×
UNCOV
81
                        return nil, fmt.Errorf("GraphQL cannot execute a request containing a %v", definition.GetKind())
×
82
                }
83
        }
84

85
        if operation == nil {
7✔
UNCOV
86
                if p.OperationName != "" {
×
UNCOV
87
                        return nil, fmt.Errorf(`Unknown operation named "%v".`, p.OperationName)
×
UNCOV
88
                }
×
UNCOV
89
                return nil, fmt.Errorf(`Must provide an operation.`)
×
90
        }
91

92
        variableValues, err := getVariableValues(p.Schema, operation.GetVariableDefinitions(), p.Args)
7✔
93
        if err != nil {
7✔
UNCOV
94
                return nil, err
×
UNCOV
95
        }
×
96

97
        eCtx.Schema = p.Schema
7✔
98
        eCtx.Fragments = fragments
7✔
99
        eCtx.Root = p.Root
7✔
100
        eCtx.Operation = operation
7✔
101
        eCtx.VariableValues = variableValues
7✔
102
        eCtx.Context = p.Context
7✔
103
        return eCtx, nil
7✔
104
}
105

106
// Extracts the root type of the operation from the schema.
107
func getOperationRootType(schema Schema, operation ast.Definition) (*Object, error) {
316✔
108
        if operation == nil {
316✔
109
                return nil, errors.New("Can only execute queries, mutations and subscription")
×
110
        }
×
111

112
        switch operation.GetOperation() {
316✔
113
        case ast.OperationTypeQuery:
285✔
114
                return schema.QueryType(), nil
285✔
115
        case ast.OperationTypeMutation:
8✔
116
                mutationType := schema.MutationType()
8✔
117
                if mutationType == nil || mutationType.PrivateName == "" {
9✔
118
                        return nil, gqlerrors.NewError(
1✔
119
                                "Schema is not configured for mutations",
1✔
120
                                []ast.Node{operation},
1✔
121
                                "",
1✔
122
                                nil,
1✔
123
                                []int{},
1✔
124
                                nil,
1✔
125
                        )
1✔
126
                }
1✔
127
                return mutationType, nil
7✔
128
        case ast.OperationTypeSubscription:
23✔
129
                subscriptionType := schema.SubscriptionType()
23✔
130
                if subscriptionType == nil || subscriptionType.PrivateName == "" {
24✔
131
                        return nil, gqlerrors.NewError(
1✔
132
                                "Schema is not configured for subscriptions",
1✔
133
                                []ast.Node{operation},
1✔
134
                                "",
1✔
135
                                nil,
1✔
136
                                []int{},
1✔
137
                                nil,
1✔
138
                        )
1✔
139
                }
1✔
140
                return subscriptionType, nil
22✔
141
        default:
×
142
                return nil, gqlerrors.NewError(
×
143
                        "Can only execute queries, mutations and subscription",
×
144
                        []ast.Node{operation},
×
145
                        "",
×
146
                        nil,
×
147
                        []int{},
×
148
                        nil,
×
149
                )
×
150
        }
151
}
152

153
// dethunkQueue is a structure that allows us to execute a classic breadth-first traversal.
154
type dethunkQueue struct {
155
        DethunkFuncs []func()
156
}
157

158
func (d *dethunkQueue) push(f func()) {
503✔
159
        d.DethunkFuncs = append(d.DethunkFuncs, f)
503✔
160
}
503✔
161

162
func (d *dethunkQueue) shift() func() {
503✔
163
        f := d.DethunkFuncs[0]
503✔
164
        d.DethunkFuncs = d.DethunkFuncs[1:]
503✔
165
        return f
503✔
166
}
503✔
167

168
// dethunkWithBreadthFirstTraversal performs a breadth-first descent of the map, calling any thunks
169
// in the map values and replacing each thunk with that thunk's return value. This parallels
170
// the reference graphql-js implementation, which calls Promise.all on thunks at each depth (which
171
// is an implicit parallel descent).
172
func dethunkMapWithBreadthFirstTraversal(finalResults map[string]interface{}) {
210✔
173
        dethunkQueue := &dethunkQueue{DethunkFuncs: []func(){}}
210✔
174
        dethunkMapBreadthFirst(finalResults, dethunkQueue)
210✔
175
        for len(dethunkQueue.DethunkFuncs) > 0 {
713✔
176
                f := dethunkQueue.shift()
503✔
177
                f()
503✔
178
        }
503✔
179
}
180

181
func dethunkMapBreadthFirst(m map[string]interface{}, dethunkQueue *dethunkQueue) {
585✔
182
        for k, v := range m {
1,973✔
183
                if f, ok := v.(func() interface{}); ok {
1,391✔
184
                        m[k] = f()
3✔
185
                }
3✔
186
                switch val := m[k].(type) {
1,388✔
187
                case map[string]interface{}:
179✔
188
                        dethunkQueue.push(func() { dethunkMapBreadthFirst(val, dethunkQueue) })
358✔
189
                case []interface{}:
128✔
190
                        dethunkQueue.push(func() { dethunkListBreadthFirst(val, dethunkQueue) })
256✔
191
                }
192
        }
193
}
194

195
func dethunkListBreadthFirst(list []interface{}, dethunkQueue *dethunkQueue) {
128✔
196
        for i, v := range list {
397✔
197
                if f, ok := v.(func() interface{}); ok {
287✔
198
                        list[i] = f()
18✔
199
                }
18✔
200
                switch val := list[i].(type) {
267✔
201
                case map[string]interface{}:
196✔
202
                        dethunkQueue.push(func() { dethunkMapBreadthFirst(val, dethunkQueue) })
392✔
203
                case []interface{}:
×
204
                        dethunkQueue.push(func() { dethunkListBreadthFirst(val, dethunkQueue) })
×
205
                }
206
        }
207
}
208

209
// dethunkMapDepthFirst performs a serial descent of the map, calling any thunks
210
// in the map values and replacing each thunk with that thunk's return value. This is needed
211
// to conform to the graphql-js reference implementation, which requires serial (depth-first)
212
// implementations for mutation selects.
213
func dethunkMapDepthFirst(m map[string]interface{}) {
16✔
214
        for k, v := range m {
40✔
215
                if f, ok := v.(func() interface{}); ok {
24✔
216
                        m[k] = f()
×
217
                }
×
218
                switch val := m[k].(type) {
24✔
219
                case map[string]interface{}:
9✔
220
                        dethunkMapDepthFirst(val)
9✔
221
                case []interface{}:
×
222
                        dethunkListDepthFirst(val)
×
223
                }
224
        }
225
}
226

227
func dethunkListDepthFirst(list []interface{}) {
×
228
        for i, v := range list {
×
229
                if f, ok := v.(func() interface{}); ok {
×
230
                        list[i] = f()
×
231
                }
×
232
                switch val := list[i].(type) {
×
233
                case map[string]interface{}:
×
234
                        dethunkMapDepthFirst(val)
×
235
                case []interface{}:
×
236
                        dethunkListDepthFirst(val)
×
237
                }
238
        }
239
}
240

241
type collectFieldsParams struct {
242
        ExeContext           *executionContext
243
        RuntimeType          *Object // previously known as OperationType
244
        SelectionSet         *ast.SelectionSet
245
        Fields               map[string][]*ast.Field
246
        VisitedFragmentNames map[string]bool
247
}
248

249
// Given a selectionSet, adds all of the fields in that selection to
250
// the passed in map of fields, and returns it at the end.
251
// CollectFields requires the "runtime type" of an object. For a field which
252
// returns and Interface or Union type, the "runtime type" will be the actual
253
// Object type returned by that field.
254
func collectFields(p collectFieldsParams) (fields map[string][]*ast.Field) {
7✔
255
        // overlying SelectionSet & Fields to fields
7✔
256
        if p.SelectionSet == nil {
7✔
257
                return p.Fields
×
258
        }
×
259
        fields = p.Fields
7✔
260
        if fields == nil {
14✔
261
                fields = map[string][]*ast.Field{}
7✔
262
        }
7✔
263
        if p.VisitedFragmentNames == nil {
14✔
264
                p.VisitedFragmentNames = map[string]bool{}
7✔
265
        }
7✔
266
        for _, iSelection := range p.SelectionSet.Selections {
14✔
267
                switch selection := iSelection.(type) {
7✔
268
                case *ast.Field:
7✔
269
                        if !shouldIncludeNode(p.ExeContext, selection.Directives) {
7✔
UNCOV
270
                                continue
×
271
                        }
272
                        name := getFieldEntryKey(selection)
7✔
273
                        if _, ok := fields[name]; !ok {
14✔
274
                                fields[name] = []*ast.Field{}
7✔
275
                        }
7✔
276
                        fields[name] = append(fields[name], selection)
7✔
UNCOV
277
                case *ast.InlineFragment:
×
UNCOV
278

×
UNCOV
279
                        if !shouldIncludeNode(p.ExeContext, selection.Directives) ||
×
UNCOV
280
                                !doesFragmentConditionMatch(p.ExeContext, selection, p.RuntimeType) {
×
UNCOV
281
                                continue
×
282
                        }
UNCOV
283
                        innerParams := collectFieldsParams{
×
UNCOV
284
                                ExeContext:           p.ExeContext,
×
UNCOV
285
                                RuntimeType:          p.RuntimeType,
×
UNCOV
286
                                SelectionSet:         selection.SelectionSet,
×
UNCOV
287
                                Fields:               fields,
×
UNCOV
288
                                VisitedFragmentNames: p.VisitedFragmentNames,
×
UNCOV
289
                        }
×
UNCOV
290
                        collectFields(innerParams)
×
UNCOV
291
                case *ast.FragmentSpread:
×
UNCOV
292
                        fragName := ""
×
UNCOV
293
                        if selection.Name != nil {
×
UNCOV
294
                                fragName = selection.Name.Value
×
UNCOV
295
                        }
×
UNCOV
296
                        if visited, ok := p.VisitedFragmentNames[fragName]; (ok && visited) ||
×
UNCOV
297
                                !shouldIncludeNode(p.ExeContext, selection.Directives) {
×
UNCOV
298
                                continue
×
299
                        }
UNCOV
300
                        p.VisitedFragmentNames[fragName] = true
×
UNCOV
301
                        fragment, hasFragment := p.ExeContext.Fragments[fragName]
×
UNCOV
302
                        if !hasFragment {
×
303
                                continue
×
304
                        }
305

UNCOV
306
                        if fragment, ok := fragment.(*ast.FragmentDefinition); ok {
×
UNCOV
307
                                if !doesFragmentConditionMatch(p.ExeContext, fragment, p.RuntimeType) {
×
308
                                        continue
×
309
                                }
UNCOV
310
                                innerParams := collectFieldsParams{
×
UNCOV
311
                                        ExeContext:           p.ExeContext,
×
UNCOV
312
                                        RuntimeType:          p.RuntimeType,
×
UNCOV
313
                                        SelectionSet:         fragment.GetSelectionSet(),
×
UNCOV
314
                                        Fields:               fields,
×
UNCOV
315
                                        VisitedFragmentNames: p.VisitedFragmentNames,
×
UNCOV
316
                                }
×
UNCOV
317
                                collectFields(innerParams)
×
318
                        }
319
                }
320
        }
321
        return fields
7✔
322
}
323

324
// Determines if a field should be included based on the @include and @skip
325
// directives, where @skip has higher precedence than @include.
326
func shouldIncludeNode(eCtx *executionContext, directives []*ast.Directive) bool {
7✔
327
        var (
7✔
328
                skipAST, includeAST *ast.Directive
7✔
329
                argValues           map[string]interface{}
7✔
330
        )
7✔
331
        for _, directive := range directives {
7✔
UNCOV
332
                if directive == nil || directive.Name == nil {
×
333
                        continue
×
334
                }
UNCOV
335
                switch directive.Name.Value {
×
UNCOV
336
                case SkipDirective.Name:
×
UNCOV
337
                        skipAST = directive
×
UNCOV
338
                case IncludeDirective.Name:
×
UNCOV
339
                        includeAST = directive
×
340
                }
341
        }
342
        // precedence: skipAST > includeAST
343
        if skipAST != nil {
7✔
UNCOV
344
                argValues = getArgumentValues(SkipDirective.Args, skipAST.Arguments, eCtx.VariableValues)
×
UNCOV
345
                if skipIf, ok := argValues["if"].(bool); ok && skipIf {
×
UNCOV
346
                        return false // excluded selectionSet's fields
×
UNCOV
347
                }
×
348
        }
349
        if includeAST != nil {
7✔
UNCOV
350
                argValues = getArgumentValues(IncludeDirective.Args, includeAST.Arguments, eCtx.VariableValues)
×
UNCOV
351
                if includeIf, ok := argValues["if"].(bool); ok && !includeIf {
×
UNCOV
352
                        return false // excluded selectionSet's fields
×
UNCOV
353
                }
×
354
        }
355
        return true
7✔
356
}
357

358
// Determines if a fragment is applicable to the given type.
UNCOV
359
func doesFragmentConditionMatch(eCtx *executionContext, fragment ast.Node, ttype *Object) bool {
×
UNCOV
360

×
UNCOV
361
        switch fragment := fragment.(type) {
×
UNCOV
362
        case *ast.FragmentDefinition:
×
UNCOV
363
                typeConditionAST := fragment.TypeCondition
×
UNCOV
364
                if typeConditionAST == nil {
×
365
                        return true
×
366
                }
×
UNCOV
367
                conditionalType, err := typeFromAST(eCtx.Schema, typeConditionAST)
×
UNCOV
368
                if err != nil {
×
369
                        return false
×
370
                }
×
UNCOV
371
                if conditionalType == ttype {
×
UNCOV
372
                        return true
×
UNCOV
373
                }
×
UNCOV
374
                if conditionalType.Name() == ttype.Name() {
×
375
                        return true
×
376
                }
×
UNCOV
377
                if conditionalType, ok := conditionalType.(*Interface); ok {
×
UNCOV
378
                        return eCtx.Schema.IsPossibleType(conditionalType, ttype)
×
UNCOV
379
                }
×
UNCOV
380
                if conditionalType, ok := conditionalType.(*Union); ok {
×
UNCOV
381
                        return eCtx.Schema.IsPossibleType(conditionalType, ttype)
×
UNCOV
382
                }
×
UNCOV
383
        case *ast.InlineFragment:
×
UNCOV
384
                typeConditionAST := fragment.TypeCondition
×
UNCOV
385
                if typeConditionAST == nil {
×
UNCOV
386
                        return true
×
UNCOV
387
                }
×
UNCOV
388
                conditionalType, err := typeFromAST(eCtx.Schema, typeConditionAST)
×
UNCOV
389
                if err != nil {
×
390
                        return false
×
391
                }
×
UNCOV
392
                if conditionalType == ttype {
×
UNCOV
393
                        return true
×
UNCOV
394
                }
×
UNCOV
395
                if conditionalType.Name() == ttype.Name() {
×
396
                        return true
×
397
                }
×
UNCOV
398
                if conditionalType, ok := conditionalType.(*Interface); ok {
×
399
                        return eCtx.Schema.IsPossibleType(conditionalType, ttype)
×
400
                }
×
UNCOV
401
                if conditionalType, ok := conditionalType.(*Union); ok {
×
402
                        return eCtx.Schema.IsPossibleType(conditionalType, ttype)
×
403
                }
×
404
        }
405

UNCOV
406
        return false
×
407
}
408

409
// Implements the logic to compute the key of a given field’s entry
410
func getFieldEntryKey(node *ast.Field) string {
969✔
411

969✔
412
        if node.Alias != nil && node.Alias.Value != "" {
1,008✔
413
                return node.Alias.Value
39✔
414
        }
39✔
415
        if node.Name != nil && node.Name.Value != "" {
1,860✔
416
                return node.Name.Value
930✔
417
        }
930✔
418
        return ""
×
419
}
420

421
func handleFieldError(r interface{}, fieldNodes []ast.Node, path *ResponsePath, returnType Output, eCtx *executionContext) {
171✔
422
        err := NewLocatedErrorWithPath(r, fieldNodes, path.AsArray())
171✔
423
        // send panic upstream
171✔
424
        if _, ok := returnType.(*NonNull); ok {
285✔
425
                panic(err)
114✔
426
        }
427
        eCtx.Errors = append(eCtx.Errors, gqlerrors.FormatError(err))
57✔
428
}
429

430
// completeLeafValue complete a leaf value (Scalar / Enum) by serializing to a valid value, returning nil if serialization is not possible.
431
func completeLeafValue(returnType Leaf, result interface{}) interface{} {
867✔
432
        serializedResult := returnType.Serialize(result)
867✔
433
        if isNullish(serializedResult) {
868✔
434
                return nil
1✔
435
        }
1✔
436
        return serializedResult
866✔
437
}
438

439
// defaultResolveTypeFn If a resolveType function is not given, then a default resolve behavior is
440
// used which tests each possible type for the abstract type by calling
441
// isTypeOf for the object being coerced, returning the first type that matches.
442
func defaultResolveTypeFn(p ResolveTypeParams, abstractType Abstract) *Object {
12✔
443
        possibleTypes := p.Info.Schema.PossibleTypes(abstractType)
12✔
444
        for _, possibleType := range possibleTypes {
34✔
445
                if possibleType.IsTypeOf == nil {
22✔
446
                        continue
×
447
                }
448
                isTypeOfParams := IsTypeOfParams{
22✔
449
                        Value:   p.Value,
22✔
450
                        Info:    p.Info,
22✔
451
                        Context: p.Context,
22✔
452
                }
22✔
453
                if res := possibleType.IsTypeOf(isTypeOfParams); res {
34✔
454
                        return possibleType
12✔
455
                }
12✔
456
        }
457
        return nil
×
458
}
459

460
// FieldResolver is used in DefaultResolveFn when the the source value implements this interface.
461
type FieldResolver interface {
462
        // Resolve resolves the value for the given ResolveParams. It has the same semantics as FieldResolveFn.
463
        Resolve(p ResolveParams) (interface{}, error)
464
}
465

466
// DefaultResolveFn If a resolve function is not given, then a default resolve behavior is used
467
// which takes the property of the source object of the same name as the field
468
// and returns it as the result, or if it's a function, returns the result
469
// of calling that function.
470
func DefaultResolveFn(p ResolveParams) (interface{}, error) {
819✔
471
        sourceVal := reflect.ValueOf(p.Source)
819✔
472
        // Check if value implements 'Resolver' interface
819✔
473
        if resolver, ok := sourceVal.Interface().(FieldResolver); ok {
821✔
474
                return resolver.Resolve(p)
2✔
475
        }
2✔
476

477
        // try to resolve p.Source as a struct
478
        if sourceVal.IsValid() && sourceVal.Type().Kind() == reflect.Ptr {
1,378✔
479
                sourceVal = sourceVal.Elem()
561✔
480
        }
561✔
481
        if !sourceVal.IsValid() {
817✔
482
                return nil, nil
×
483
        }
×
484

485
        if sourceVal.Type().Kind() == reflect.Struct {
1,416✔
486
                for i := 0; i < sourceVal.NumField(); i++ {
1,836✔
487
                        valueField := sourceVal.Field(i)
1,237✔
488
                        typeField := sourceVal.Type().Field(i)
1,237✔
489
                        // try matching the field name first
1,237✔
490
                        if strings.EqualFold(typeField.Name, p.Info.FieldName) {
1,646✔
491
                                return valueField.Interface(), nil
409✔
492
                        }
409✔
493
                        tag := typeField.Tag
828✔
494
                        checkTag := func(tagName string) bool {
2,378✔
495
                                t := tag.Get(tagName)
1,550✔
496
                                tOptions := strings.Split(t, ",")
1,550✔
497
                                if len(tOptions) == 0 {
1,550✔
498
                                        return false
×
499
                                }
×
500
                                if tOptions[0] != p.Info.FieldName {
2,994✔
501
                                        return false
1,444✔
502
                                }
1,444✔
503
                                return true
106✔
504
                        }
505
                        if checkTag("json") || checkTag("graphql") {
934✔
506
                                return valueField.Interface(), nil
106✔
507
                        } else {
828✔
508
                                continue
722✔
509
                        }
510
                }
511
                return nil, nil
84✔
512
        }
513

514
        // try p.Source as a map[string]interface
515
        if sourceMap, ok := p.Source.(map[string]interface{}); ok {
435✔
516
                property := sourceMap[p.Info.FieldName]
217✔
517
                val := reflect.ValueOf(property)
217✔
518
                if val.IsValid() && val.Type().Kind() == reflect.Func {
396✔
519
                        // try type casting the func to the most basic func signature
179✔
520
                        // for more complex signatures, user have to define ResolveFn
179✔
521
                        if propertyFn, ok := property.(func() interface{}); ok {
358✔
522
                                return propertyFn(), nil
179✔
523
                        }
179✔
524
                }
525
                return property, nil
38✔
526
        }
527

528
        // Try accessing as map via reflection
529
        if r := reflect.ValueOf(p.Source); r.Kind() == reflect.Map && r.Type().Key().Kind() == reflect.String {
2✔
530
                val := r.MapIndex(reflect.ValueOf(p.Info.FieldName))
1✔
531
                if val.IsValid() {
2✔
532
                        property := val.Interface()
1✔
533
                        if val.Type().Kind() == reflect.Func {
1✔
534
                                // try type casting the func to the most basic func signature
×
535
                                // for more complex signatures, user have to define ResolveFn
×
536
                                if propertyFn, ok := property.(func() interface{}); ok {
×
537
                                        return propertyFn(), nil
×
538
                                }
×
539
                        }
540
                        return property, nil
1✔
541
                }
542
        }
543

544
        // last resort, return nil
545
        return nil, nil
×
546
}
547

548
// This method looks up the field on the given type definition.
549
// It has special casing for the two introspection fields, __schema
550
// and __typename. __typename is special because it can always be
551
// queried as a field, even in situations where no other fields
552
// are allowed, like on a Union. __schema could get automatically
553
// added to the query type, but that would require mutating type
554
// definitions, which would cause issues.
555
func getFieldDef(schema Schema, parentType *Object, fieldName string) *FieldDefinition {
1,002✔
556

1,002✔
557
        if parentType == nil {
1,002✔
558
                return nil
×
559
        }
×
560

561
        if fieldName == SchemaMetaFieldDef.Name &&
1,002✔
562
                schema.QueryType() == parentType {
1,004✔
563
                return SchemaMetaFieldDef
2✔
564
        }
2✔
565
        if fieldName == TypeMetaFieldDef.Name &&
1,000✔
566
                schema.QueryType() == parentType {
1,009✔
567
                return TypeMetaFieldDef
9✔
568
        }
9✔
569
        if fieldName == TypeNameMetaFieldDef.Name {
1,015✔
570
                return TypeNameMetaFieldDef
24✔
571
        }
24✔
572
        return parentType.Fields()[fieldName]
967✔
573
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc