@@ -227,6 +227,21 @@ type executeFieldsParams struct {
227
227
Path * responsePath
228
228
}
229
229
230
+ // dethunkQueue is a structure that allows us to execute a classic breadth-first search.
231
+ type dethunkQueue struct {
232
+ DethunkFuncs []func ()
233
+ }
234
+
235
+ func (d * dethunkQueue ) push (f func ()) {
236
+ d .DethunkFuncs = append (d .DethunkFuncs , f )
237
+ }
238
+
239
+ func (d * dethunkQueue ) shift () func () {
240
+ f := d .DethunkFuncs [0 ]
241
+ d .DethunkFuncs = d .DethunkFuncs [1 :]
242
+ return f
243
+ }
244
+
230
245
// Implements the "Evaluating selection sets" section of the spec for "write" mode.
231
246
func executeFieldsSerially (p executeFieldsParams ) * Result {
232
247
if p .Source == nil {
@@ -245,15 +260,36 @@ func executeFieldsSerially(p executeFieldsParams) *Result {
245
260
}
246
261
finalResults [responseName ] = resolved
247
262
}
263
+ dethunkWithBreadthFirstSearch (finalResults )
248
264
249
265
return & Result {
250
266
Data : finalResults ,
251
267
Errors : p .ExecutionContext .Errors ,
252
268
}
253
269
}
254
270
271
+ func dethunkWithBreadthFirstSearch (finalResults map [string ]interface {}) {
272
+ dethunkQueue := & dethunkQueue {DethunkFuncs : []func (){}}
273
+ dethunkMap (finalResults , dethunkQueue )
274
+ for len (dethunkQueue .DethunkFuncs ) > 0 {
275
+ f := dethunkQueue .shift ()
276
+ f ()
277
+ }
278
+ }
279
+
255
280
// Implements the "Evaluating selection sets" section of the spec for "read" mode.
256
281
func executeFields (p executeFieldsParams ) * Result {
282
+ finalResults := executeSubFields (p )
283
+
284
+ dethunkWithBreadthFirstSearch (finalResults )
285
+
286
+ return & Result {
287
+ Data : finalResults ,
288
+ Errors : p .ExecutionContext .Errors ,
289
+ }
290
+ }
291
+
292
+ func executeSubFields (p executeFieldsParams ) map [string ]interface {} {
257
293
if p .Source == nil {
258
294
p .Source = map [string ]interface {}{}
259
295
}
@@ -271,9 +307,42 @@ func executeFields(p executeFieldsParams) *Result {
271
307
finalResults [responseName ] = resolved
272
308
}
273
309
274
- return & Result {
275
- Data : finalResults ,
276
- Errors : p .ExecutionContext .Errors ,
310
+ return finalResults
311
+ }
312
+
313
+ // dethunkMap performs a breadth-first descent of the map, calling any thunks
314
+ // in the map values and replacing each thunk with that thunk's return value.
315
+ func dethunkMap (m map [string ]interface {}, dethunkQueue * dethunkQueue ) {
316
+ for k , v := range m {
317
+ if f , ok := v .(func () interface {}); ok {
318
+ m [k ] = f ()
319
+ }
320
+ }
321
+ for _ , v := range m {
322
+ switch val := v .(type ) {
323
+ case map [string ]interface {}:
324
+ dethunkQueue .push (func () { dethunkMap (val , dethunkQueue ) })
325
+ case []interface {}:
326
+ dethunkQueue .push (func () { dethunkList (val , dethunkQueue ) })
327
+ }
328
+ }
329
+ }
330
+
331
+ // dethunkList iterates through the list, calling any thunks in the list
332
+ // and replacing each thunk with that thunk's return value.
333
+ func dethunkList (list []interface {}, dethunkQueue * dethunkQueue ) {
334
+ for i , v := range list {
335
+ if f , ok := v .(func () interface {}); ok {
336
+ list [i ] = f ()
337
+ }
338
+ }
339
+ for _ , v := range list {
340
+ switch val := v .(type ) {
341
+ case map [string ]interface {}:
342
+ dethunkQueue .push (func () { dethunkMap (val , dethunkQueue ) })
343
+ case []interface {}:
344
+ dethunkQueue .push (func () { dethunkList (val , dethunkQueue ) })
345
+ }
277
346
}
278
347
}
279
348
@@ -558,13 +627,9 @@ func completeValueCatchingError(eCtx *executionContext, returnType Type, fieldAS
558
627
func completeValue (eCtx * executionContext , returnType Type , fieldASTs []* ast.Field , info ResolveInfo , path * responsePath , result interface {}) interface {} {
559
628
560
629
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 ))
630
+ if resultVal .IsValid () && resultVal .Kind () == reflect .Func {
631
+ return func () interface {} {
632
+ return completeThunkValueCatchingError (eCtx , returnType , fieldASTs , info , path , result )
568
633
}
569
634
}
570
635
@@ -626,6 +691,30 @@ func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Fie
626
691
return nil
627
692
}
628
693
694
+ func completeThunkValueCatchingError (eCtx * executionContext , returnType Type , fieldASTs []* ast.Field , info ResolveInfo , path * responsePath , result interface {}) (completed interface {}) {
695
+
696
+ // catch any panic invoked from the propertyFn (thunk)
697
+ defer func () {
698
+ if r := recover (); r != nil {
699
+ handleFieldError (r , FieldASTsToNodeASTs (fieldASTs ), path , returnType , eCtx )
700
+ }
701
+ }()
702
+
703
+ propertyFn , ok := result .(func () interface {})
704
+ if ! ok {
705
+ err := gqlerrors .NewFormattedError ("Error resolving func. Expected `func() interface{}` signature" )
706
+ panic (gqlerrors .FormatError (err ))
707
+ }
708
+ result = propertyFn ()
709
+
710
+ if returnType , ok := returnType .(* NonNull ); ok {
711
+ completed := completeValue (eCtx , returnType , fieldASTs , info , path , result )
712
+ return completed
713
+ }
714
+ completed = completeValue (eCtx , returnType , fieldASTs , info , path , result )
715
+ return completed
716
+ }
717
+
629
718
// completeAbstractValue completes value of an Abstract type (Union / Interface) by determining the runtime type
630
719
// of that value, then completing based on that type.
631
720
func completeAbstractValue (eCtx * executionContext , returnType Abstract , fieldASTs []* ast.Field , info ResolveInfo , path * responsePath , result interface {}) interface {} {
@@ -709,10 +798,7 @@ func completeObjectValue(eCtx *executionContext, returnType *Object, fieldASTs [
709
798
Fields : subFieldASTs ,
710
799
Path : path ,
711
800
}
712
- results := executeFields (executeFieldsParams )
713
-
714
- return results .Data
715
-
801
+ return executeSubFields (executeFieldsParams )
716
802
}
717
803
718
804
// completeLeafValue complete a leaf value (Scalar / Enum) by serializing to a valid value, returning nil if serialization is not possible.
0 commit comments