Skip to content

Commit 5eec4f9

Browse files
authored
Merge pull request #363 from ccbrown/error-spec-catchup
Implement June 2018 error spec
2 parents 086b3bf + 8752c8e commit 5eec4f9

File tree

9 files changed

+533
-59
lines changed

9 files changed

+533
-59
lines changed

abstract_test.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -507,8 +507,17 @@ func TestResolveTypeOnInterfaceYieldsUsefulError(t *testing.T) {
507507
},
508508
Errors: []gqlerrors.FormattedError{
509509
{
510-
Message: `Runtime Object type "Human" is not a possible type for "Pet".`,
511-
Locations: []location.SourceLocation{},
510+
Message: `Runtime Object type "Human" is not a possible type for "Pet".`,
511+
Locations: []location.SourceLocation{
512+
{
513+
Line: 2,
514+
Column: 7,
515+
},
516+
},
517+
Path: []interface{}{
518+
"pets",
519+
2,
520+
},
512521
},
513522
},
514523
}
@@ -625,8 +634,17 @@ func TestResolveTypeOnUnionYieldsUsefulError(t *testing.T) {
625634
},
626635
Errors: []gqlerrors.FormattedError{
627636
{
628-
Message: `Runtime Object type "Human" is not a possible type for "Pet".`,
629-
Locations: []location.SourceLocation{},
637+
Message: `Runtime Object type "Human" is not a possible type for "Pet".`,
638+
Locations: []location.SourceLocation{
639+
{
640+
Line: 2,
641+
Column: 7,
642+
},
643+
},
644+
Path: []interface{}{
645+
"pets",
646+
2,
647+
},
630648
},
631649
},
632650
}

definition.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,3 +1288,24 @@ func assertValidName(name string) error {
12881288
`Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "%v" does not.`, name)
12891289

12901290
}
1291+
1292+
type responsePath struct {
1293+
Prev *responsePath
1294+
Key interface{}
1295+
}
1296+
1297+
// WithKey returns a new responsePath containing the new key.
1298+
func (p *responsePath) WithKey(key interface{}) *responsePath {
1299+
return &responsePath{
1300+
Prev: p,
1301+
Key: key,
1302+
}
1303+
}
1304+
1305+
// AsArray returns an array of path keys.
1306+
func (p *responsePath) AsArray() []interface{} {
1307+
if p == nil {
1308+
return nil
1309+
}
1310+
return append(p.Prev.AsArray(), p.Key)
1311+
}

executor.go

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ type executeFieldsParams struct {
224224
ParentType *Object
225225
Source interface{}
226226
Fields map[string][]*ast.Field
227+
Path *responsePath
227228
}
228229

229230
// Implements the "Evaluating selection sets" section of the spec for "write" mode.
@@ -237,7 +238,8 @@ func executeFieldsSerially(p executeFieldsParams) *Result {
237238

238239
finalResults := make(map[string]interface{}, len(p.Fields))
239240
for responseName, fieldASTs := range p.Fields {
240-
resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs)
241+
fieldPath := p.Path.WithKey(responseName)
242+
resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs, fieldPath)
241243
if state.hasNoFieldDefs {
242244
continue
243245
}
@@ -261,7 +263,8 @@ func executeFields(p executeFieldsParams) *Result {
261263

262264
finalResults := make(map[string]interface{}, len(p.Fields))
263265
for responseName, fieldASTs := range p.Fields {
264-
resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs)
266+
fieldPath := p.Path.WithKey(responseName)
267+
resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs, fieldPath)
265268
if state.hasNoFieldDefs {
266269
continue
267270
}
@@ -459,31 +462,25 @@ type resolveFieldResultState struct {
459462
hasNoFieldDefs bool
460463
}
461464

465+
func handleFieldError(r interface{}, fieldNodes []ast.Node, path *responsePath, returnType Output, eCtx *executionContext) {
466+
err := NewLocatedErrorWithPath(r, fieldNodes, path.AsArray())
467+
// send panic upstream
468+
if _, ok := returnType.(*NonNull); ok {
469+
panic(err)
470+
}
471+
eCtx.Errors = append(eCtx.Errors, gqlerrors.FormatError(err))
472+
}
473+
462474
// Resolves the field on the given source object. In particular, this
463475
// figures out the value that the field returns by calling its resolve function,
464476
// then calls completeValue to complete promises, serialize scalars, or execute
465477
// the sub-selection-set for objects.
466-
func resolveField(eCtx *executionContext, parentType *Object, source interface{}, fieldASTs []*ast.Field) (result interface{}, resultState resolveFieldResultState) {
478+
func resolveField(eCtx *executionContext, parentType *Object, source interface{}, fieldASTs []*ast.Field, path *responsePath) (result interface{}, resultState resolveFieldResultState) {
467479
// catch panic from resolveFn
468480
var returnType Output
469481
defer func() (interface{}, resolveFieldResultState) {
470482
if r := recover(); r != nil {
471-
472-
var err error
473-
if r, ok := r.(string); ok {
474-
err = NewLocatedError(
475-
fmt.Sprintf("%v", r),
476-
FieldASTsToNodeASTs(fieldASTs),
477-
)
478-
}
479-
if r, ok := r.(error); ok {
480-
err = gqlerrors.FormatError(r)
481-
}
482-
// send panic upstream
483-
if _, ok := returnType.(*NonNull); ok {
484-
panic(gqlerrors.FormatError(err))
485-
}
486-
eCtx.Errors = append(eCtx.Errors, gqlerrors.FormatError(err))
483+
handleFieldError(r, FieldASTsToNodeASTs(fieldASTs), path, returnType, eCtx)
487484
return result, resultState
488485
}
489486
return result, resultState
@@ -533,38 +530,32 @@ func resolveField(eCtx *executionContext, parentType *Object, source interface{}
533530
})
534531

535532
if resolveFnError != nil {
536-
panic(gqlerrors.FormatError(resolveFnError))
533+
panic(resolveFnError)
537534
}
538535

539-
completed := completeValueCatchingError(eCtx, returnType, fieldASTs, info, result)
536+
completed := completeValueCatchingError(eCtx, returnType, fieldASTs, info, path, result)
540537
return completed, resultState
541538
}
542539

543-
func completeValueCatchingError(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (completed interface{}) {
540+
func completeValueCatchingError(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *responsePath, result interface{}) (completed interface{}) {
544541
// catch panic
545542
defer func() interface{} {
546543
if r := recover(); r != nil {
547-
//send panic upstream
548-
if _, ok := returnType.(*NonNull); ok {
549-
panic(r)
550-
}
551-
if err, ok := r.(gqlerrors.FormattedError); ok {
552-
eCtx.Errors = append(eCtx.Errors, err)
553-
}
544+
handleFieldError(r, FieldASTsToNodeASTs(fieldASTs), path, returnType, eCtx)
554545
return completed
555546
}
556547
return completed
557548
}()
558549

559550
if returnType, ok := returnType.(*NonNull); ok {
560-
completed := completeValue(eCtx, returnType, fieldASTs, info, result)
551+
completed := completeValue(eCtx, returnType, fieldASTs, info, path, result)
561552
return completed
562553
}
563-
completed = completeValue(eCtx, returnType, fieldASTs, info, result)
554+
completed = completeValue(eCtx, returnType, fieldASTs, info, path, result)
564555
return completed
565556
}
566557

567-
func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} {
558+
func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, path *responsePath, result interface{}) interface{} {
568559

569560
resultVal := reflect.ValueOf(result)
570561
for resultVal.IsValid() && resultVal.Type().Kind() == reflect.Func {
@@ -580,11 +571,12 @@ func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Fie
580571
// If field type is NonNull, complete for inner type, and throw field error
581572
// if result is null.
582573
if returnType, ok := returnType.(*NonNull); ok {
583-
completed := completeValue(eCtx, returnType.OfType, fieldASTs, info, result)
574+
completed := completeValue(eCtx, returnType.OfType, fieldASTs, info, path, result)
584575
if completed == nil {
585-
err := NewLocatedError(
576+
err := NewLocatedErrorWithPath(
586577
fmt.Sprintf("Cannot return null for non-nullable field %v.%v.", info.ParentType, info.FieldName),
587578
FieldASTsToNodeASTs(fieldASTs),
579+
path.AsArray(),
588580
)
589581
panic(gqlerrors.FormatError(err))
590582
}
@@ -598,7 +590,7 @@ func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Fie
598590

599591
// If field type is List, complete each item in the list with the inner type
600592
if returnType, ok := returnType.(*List); ok {
601-
return completeListValue(eCtx, returnType, fieldASTs, info, result)
593+
return completeListValue(eCtx, returnType, fieldASTs, info, path, result)
602594
}
603595

604596
// If field type is a leaf type, Scalar or Enum, serialize to a valid value,
@@ -613,15 +605,15 @@ func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Fie
613605
// If field type is an abstract type, Interface or Union, determine the
614606
// runtime Object type and complete for that type.
615607
if returnType, ok := returnType.(*Union); ok {
616-
return completeAbstractValue(eCtx, returnType, fieldASTs, info, result)
608+
return completeAbstractValue(eCtx, returnType, fieldASTs, info, path, result)
617609
}
618610
if returnType, ok := returnType.(*Interface); ok {
619-
return completeAbstractValue(eCtx, returnType, fieldASTs, info, result)
611+
return completeAbstractValue(eCtx, returnType, fieldASTs, info, path, result)
620612
}
621613

622614
// If field type is Object, execute and complete all sub-selections.
623615
if returnType, ok := returnType.(*Object); ok {
624-
return completeObjectValue(eCtx, returnType, fieldASTs, info, result)
616+
return completeObjectValue(eCtx, returnType, fieldASTs, info, path, result)
625617
}
626618

627619
// Not reachable. All possible output types have been considered.
@@ -636,7 +628,7 @@ func completeValue(eCtx *executionContext, returnType Type, fieldASTs []*ast.Fie
636628

637629
// completeAbstractValue completes value of an Abstract type (Union / Interface) by determining the runtime type
638630
// of that value, then completing based on that type.
639-
func completeAbstractValue(eCtx *executionContext, returnType Abstract, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} {
631+
func completeAbstractValue(eCtx *executionContext, returnType Abstract, fieldASTs []*ast.Field, info ResolveInfo, path *responsePath, result interface{}) interface{} {
640632

641633
var runtimeType *Object
642634

@@ -669,11 +661,11 @@ func completeAbstractValue(eCtx *executionContext, returnType Abstract, fieldAST
669661
))
670662
}
671663

672-
return completeObjectValue(eCtx, runtimeType, fieldASTs, info, result)
664+
return completeObjectValue(eCtx, runtimeType, fieldASTs, info, path, result)
673665
}
674666

675667
// completeObjectValue complete an Object value by executing all sub-selections.
676-
func completeObjectValue(eCtx *executionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} {
668+
func completeObjectValue(eCtx *executionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, path *responsePath, result interface{}) interface{} {
677669

678670
// If there is an isTypeOf predicate function, call it with the
679671
// current result. If isTypeOf returns false, then raise an error rather
@@ -715,6 +707,7 @@ func completeObjectValue(eCtx *executionContext, returnType *Object, fieldASTs [
715707
ParentType: returnType,
716708
Source: result,
717709
Fields: subFieldASTs,
710+
Path: path,
718711
}
719712
results := executeFields(executeFieldsParams)
720713

@@ -732,7 +725,7 @@ func completeLeafValue(returnType Leaf, result interface{}) interface{} {
732725
}
733726

734727
// completeListValue complete a list value by completing each item in the list with the inner type
735-
func completeListValue(eCtx *executionContext, returnType *List, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} {
728+
func completeListValue(eCtx *executionContext, returnType *List, fieldASTs []*ast.Field, info ResolveInfo, path *responsePath, result interface{}) interface{} {
736729
resultVal := reflect.ValueOf(result)
737730
parentTypeName := ""
738731
if info.ParentType != nil {
@@ -751,7 +744,8 @@ func completeListValue(eCtx *executionContext, returnType *List, fieldASTs []*as
751744
completedResults := make([]interface{}, 0, resultVal.Len())
752745
for i := 0; i < resultVal.Len(); i++ {
753746
val := resultVal.Index(i).Interface()
754-
completedItem := completeValueCatchingError(eCtx, itemType, fieldASTs, info, val)
747+
fieldPath := path.WithKey(i)
748+
completedItem := completeValueCatchingError(eCtx, itemType, fieldASTs, info, fieldPath, val)
755749
completedResults = append(completedResults, completedItem)
756750
}
757751
return completedResults

0 commit comments

Comments
 (0)