Skip to content

Commit 094d00f

Browse files
committed
Implemented true breadth-first-search on 367.
1 parent ef7caf8 commit 094d00f

File tree

1 file changed

+100
-14
lines changed

1 file changed

+100
-14
lines changed

executor.go

Lines changed: 100 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,21 @@ type executeFieldsParams struct {
227227
Path *responsePath
228228
}
229229

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+
230245
// Implements the "Evaluating selection sets" section of the spec for "write" mode.
231246
func executeFieldsSerially(p executeFieldsParams) *Result {
232247
if p.Source == nil {
@@ -245,15 +260,36 @@ func executeFieldsSerially(p executeFieldsParams) *Result {
245260
}
246261
finalResults[responseName] = resolved
247262
}
263+
dethunkWithBreadthFirstSearch(finalResults)
248264

249265
return &Result{
250266
Data: finalResults,
251267
Errors: p.ExecutionContext.Errors,
252268
}
253269
}
254270

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+
255280
// Implements the "Evaluating selection sets" section of the spec for "read" mode.
256281
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{} {
257293
if p.Source == nil {
258294
p.Source = map[string]interface{}{}
259295
}
@@ -271,9 +307,42 @@ func executeFields(p executeFieldsParams) *Result {
271307
finalResults[responseName] = resolved
272308
}
273309

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+
}
277346
}
278347
}
279348

@@ -558,13 +627,9 @@ func completeValueCatchingError(eCtx *executionContext, returnType Type, fieldAS
558627
func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *responsePath, result interface{}) interface{} {
559628

560629
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)
568633
}
569634
}
570635

@@ -626,6 +691,30 @@ func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Fie
626691
return nil
627692
}
628693

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+
629718
// completeAbstractValue completes value of an Abstract type (Union / Interface) by determining the runtime type
630719
// of that value, then completing based on that type.
631720
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 [
709798
Fields: subFieldASTs,
710799
Path: path,
711800
}
712-
results := executeFields(executeFieldsParams)
713-
714-
return results.Data
715-
801+
return executeSubFields(executeFieldsParams)
716802
}
717803

718804
// completeLeafValue complete a leaf value (Scalar / Enum) by serializing to a valid value, returning nil if serialization is not possible.

0 commit comments

Comments
 (0)