@@ -245,6 +245,7 @@ func executeFieldsSerially(p executeFieldsParams) *Result {
245
245
}
246
246
finalResults [responseName ] = resolved
247
247
}
248
+ dethunkMapDepthFirst (finalResults )
248
249
249
250
return & Result {
250
251
Data : finalResults ,
@@ -254,6 +255,17 @@ func executeFieldsSerially(p executeFieldsParams) *Result {
254
255
255
256
// Implements the "Evaluating selection sets" section of the spec for "read" mode.
256
257
func executeFields (p executeFieldsParams ) * Result {
258
+ finalResults := executeSubFields (p )
259
+
260
+ dethunkMapWithBreadthFirstTraversal (finalResults )
261
+
262
+ return & Result {
263
+ Data : finalResults ,
264
+ Errors : p .ExecutionContext .Errors ,
265
+ }
266
+ }
267
+
268
+ func executeSubFields (p executeFieldsParams ) map [string ]interface {} {
257
269
if p .Source == nil {
258
270
p .Source = map [string ]interface {}{}
259
271
}
@@ -271,9 +283,94 @@ func executeFields(p executeFieldsParams) *Result {
271
283
finalResults [responseName ] = resolved
272
284
}
273
285
274
- return & Result {
275
- Data : finalResults ,
276
- Errors : p .ExecutionContext .Errors ,
286
+ return finalResults
287
+ }
288
+
289
+ // dethunkQueue is a structure that allows us to execute a classic breadth-first traversal.
290
+ type dethunkQueue struct {
291
+ DethunkFuncs []func ()
292
+ }
293
+
294
+ func (d * dethunkQueue ) push (f func ()) {
295
+ d .DethunkFuncs = append (d .DethunkFuncs , f )
296
+ }
297
+
298
+ func (d * dethunkQueue ) shift () func () {
299
+ f := d .DethunkFuncs [0 ]
300
+ d .DethunkFuncs = d .DethunkFuncs [1 :]
301
+ return f
302
+ }
303
+
304
+ // dethunkWithBreadthFirstTraversal performs a breadth-first descent of the map, calling any thunks
305
+ // in the map values and replacing each thunk with that thunk's return value. This parallels
306
+ // the reference graphql-js implementation, which calls Promise.all on thunks at each depth (which
307
+ // is an implicit parallel descent).
308
+ func dethunkMapWithBreadthFirstTraversal (finalResults map [string ]interface {}) {
309
+ dethunkQueue := & dethunkQueue {DethunkFuncs : []func (){}}
310
+ dethunkMapBreadthFirst (finalResults , dethunkQueue )
311
+ for len (dethunkQueue .DethunkFuncs ) > 0 {
312
+ f := dethunkQueue .shift ()
313
+ f ()
314
+ }
315
+ }
316
+
317
+ func dethunkMapBreadthFirst (m map [string ]interface {}, dethunkQueue * dethunkQueue ) {
318
+ for k , v := range m {
319
+ if f , ok := v .(func () interface {}); ok {
320
+ m [k ] = f ()
321
+ }
322
+ switch val := m [k ].(type ) {
323
+ case map [string ]interface {}:
324
+ dethunkQueue .push (func () { dethunkMapBreadthFirst (val , dethunkQueue ) })
325
+ case []interface {}:
326
+ dethunkQueue .push (func () { dethunkListBreadthFirst (val , dethunkQueue ) })
327
+ }
328
+ }
329
+ }
330
+
331
+ func dethunkListBreadthFirst (list []interface {}, dethunkQueue * dethunkQueue ) {
332
+ for i , v := range list {
333
+ if f , ok := v .(func () interface {}); ok {
334
+ list [i ] = f ()
335
+ }
336
+ switch val := list [i ].(type ) {
337
+ case map [string ]interface {}:
338
+ dethunkQueue .push (func () { dethunkMapBreadthFirst (val , dethunkQueue ) })
339
+ case []interface {}:
340
+ dethunkQueue .push (func () { dethunkListBreadthFirst (val , dethunkQueue ) })
341
+ }
342
+ }
343
+ }
344
+
345
+ // dethunkMapDepthFirst performs a serial descent of the map, calling any thunks
346
+ // in the map values and replacing each thunk with that thunk's return value. This is needed
347
+ // to conform to the graphql-js reference implementation, which requires serial (depth-first)
348
+ // implementations for mutation selects.
349
+ func dethunkMapDepthFirst (m map [string ]interface {}) {
350
+ for k , v := range m {
351
+ if f , ok := v .(func () interface {}); ok {
352
+ m [k ] = f ()
353
+ }
354
+ switch val := m [k ].(type ) {
355
+ case map [string ]interface {}:
356
+ dethunkMapDepthFirst (val )
357
+ case []interface {}:
358
+ dethunkListDepthFirst (val )
359
+ }
360
+ }
361
+ }
362
+
363
+ func dethunkListDepthFirst (list []interface {}) {
364
+ for i , v := range list {
365
+ if f , ok := v .(func () interface {}); ok {
366
+ list [i ] = f ()
367
+ }
368
+ switch val := list [i ].(type ) {
369
+ case map [string ]interface {}:
370
+ dethunkMapDepthFirst (val )
371
+ case []interface {}:
372
+ dethunkListDepthFirst (val )
373
+ }
277
374
}
278
375
}
279
376
@@ -558,13 +655,9 @@ func completeValueCatchingError(eCtx *executionContext, returnType Type, fieldAS
558
655
func completeValue (eCtx * executionContext , returnType Type , fieldASTs []* ast.Field , info ResolveInfo , path * responsePath , result interface {}) interface {} {
559
656
560
657
resultVal := reflect .ValueOf (result )
561
- for resultVal .IsValid () && resultVal .Type ().Kind () == reflect .Func {
562
- if propertyFn , ok := result .(func () interface {}); ok {
563
- result = propertyFn ()
564
- resultVal = reflect .ValueOf (result )
565
- } else {
566
- err := gqlerrors .NewFormattedError ("Error resolving func. Expected `func() interface{}` signature" )
567
- panic (gqlerrors .FormatError (err ))
658
+ if resultVal .IsValid () && resultVal .Kind () == reflect .Func {
659
+ return func () interface {} {
660
+ return completeThunkValueCatchingError (eCtx , returnType , fieldASTs , info , path , result )
568
661
}
569
662
}
570
663
@@ -626,6 +719,30 @@ func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Fie
626
719
return nil
627
720
}
628
721
722
+ func completeThunkValueCatchingError (eCtx * executionContext , returnType Type , fieldASTs []* ast.Field , info ResolveInfo , path * responsePath , result interface {}) (completed interface {}) {
723
+
724
+ // catch any panic invoked from the propertyFn (thunk)
725
+ defer func () {
726
+ if r := recover (); r != nil {
727
+ handleFieldError (r , FieldASTsToNodeASTs (fieldASTs ), path , returnType , eCtx )
728
+ }
729
+ }()
730
+
731
+ propertyFn , ok := result .(func () interface {})
732
+ if ! ok {
733
+ err := gqlerrors .NewFormattedError ("Error resolving func. Expected `func() interface{}` signature" )
734
+ panic (gqlerrors .FormatError (err ))
735
+ }
736
+ result = propertyFn ()
737
+
738
+ if returnType , ok := returnType .(* NonNull ); ok {
739
+ completed := completeValue (eCtx , returnType , fieldASTs , info , path , result )
740
+ return completed
741
+ }
742
+ completed = completeValue (eCtx , returnType , fieldASTs , info , path , result )
743
+ return completed
744
+ }
745
+
629
746
// completeAbstractValue completes value of an Abstract type (Union / Interface) by determining the runtime type
630
747
// of that value, then completing based on that type.
631
748
func completeAbstractValue (eCtx * executionContext , returnType Abstract , fieldASTs []* ast.Field , info ResolveInfo , path * responsePath , result interface {}) interface {} {
@@ -709,10 +826,7 @@ func completeObjectValue(eCtx *executionContext, returnType *Object, fieldASTs [
709
826
Fields : subFieldASTs ,
710
827
Path : path ,
711
828
}
712
- results := executeFields (executeFieldsParams )
713
-
714
- return results .Data
715
-
829
+ return executeSubFields (executeFieldsParams )
716
830
}
717
831
718
832
// completeLeafValue complete a leaf value (Scalar / Enum) by serializing to a valid value, returning nil if serialization is not possible.
0 commit comments