Skip to content
This repository has been archived by the owner on Nov 8, 2017. It is now read-only.

Commit

Permalink
executor: Don't use panic/recover to pass errors around
Browse files Browse the repository at this point in the history
Signed-off-by: Jesse Stuart <[email protected]>
  • Loading branch information
jvatic committed Oct 11, 2016
1 parent 3e4ba97 commit 66ec6ba
Showing 1 changed file with 53 additions and 103 deletions.
156 changes: 53 additions & 103 deletions executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,26 +215,7 @@ type ExecuteFieldsParams struct {

// Implements the "Evaluating selection sets" section of the spec for "write" mode.
func executeFieldsSerially(p ExecuteFieldsParams) *Result {
if p.Source == nil {
p.Source = map[string]interface{}{}
}
if p.Fields == nil {
p.Fields = map[string][]*ast.Field{}
}

finalResults := map[string]interface{}{}
for responseName, fieldASTs := range p.Fields {
resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs)
if state.hasNoFieldDefs {
continue
}
finalResults[responseName] = resolved
}

return &Result{
Data: finalResults,
Errors: p.ExecutionContext.Errors,
}
return executeFields(p)
}

// Implements the "Evaluating selection sets" section of the spec for "read" mode.
Expand All @@ -247,17 +228,25 @@ func executeFields(p ExecuteFieldsParams) *Result {
}

finalResults := map[string]interface{}{}
var errors []gqlerrors.FormattedError
for responseName, fieldASTs := range p.Fields {
resolved, state := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs)
resolved, state, err := resolveField(p.ExecutionContext, p.ParentType, p.Source, fieldASTs)
if state.hasNoFieldDefs {
continue
}
finalResults[responseName] = resolved
if err != nil {
errors = append(errors, gqlerrors.FormatError(err))
}
}

for _, err := range p.ExecutionContext.Errors {
errors = append(errors, err)
}

return &Result{
Data: finalResults,
Errors: p.ExecutionContext.Errors,
Errors: errors,
}
}

Expand Down Expand Up @@ -475,44 +464,21 @@ type resolveFieldResultState struct {
// figures out the value that the field returns by calling its resolve function,
// then calls completeValue to complete promises, serialize scalars, or execute
// the sub-selection-set for objects.
func resolveField(eCtx *ExecutionContext, parentType *Object, source interface{}, fieldASTs []*ast.Field) (result interface{}, resultState resolveFieldResultState) {
// catch panic from resolveFn
var returnType Output
defer func() (interface{}, resolveFieldResultState) {
if r := recover(); r != nil {

var err error
if r, ok := r.(string); ok {
err = NewLocatedError(
fmt.Sprintf("%v", r),
FieldASTsToNodeASTs(fieldASTs),
)
}
if r, ok := r.(error); ok {
err = gqlerrors.FormatError(r)
}
// send panic upstream
if _, ok := returnType.(*NonNull); ok {
panic(gqlerrors.FormatError(err))
}
eCtx.Errors = append(eCtx.Errors, gqlerrors.FormatError(err))
return result, resultState
}
return result, resultState
}()

func resolveField(eCtx *ExecutionContext, parentType *Object, source interface{}, fieldASTs []*ast.Field) (interface{}, resolveFieldResultState, error) {
fieldAST := fieldASTs[0]
fieldName := ""
if fieldAST.Name != nil {
fieldName = fieldAST.Name.Value
}

resultState := resolveFieldResultState{}

fieldDef := getFieldDef(eCtx.Schema, parentType, fieldName)
if fieldDef == nil {
resultState.hasNoFieldDefs = true
return nil, resultState
return nil, resultState, nil
}
returnType = fieldDef.Type
returnType := fieldDef.Type
resolveFn := fieldDef.Resolve
if resolveFn == nil {
resolveFn = defaultResolveFn
Expand All @@ -535,75 +501,56 @@ func resolveField(eCtx *ExecutionContext, parentType *Object, source interface{}
VariableValues: eCtx.VariableValues,
}

var resolveFnError error

result, resolveFnError = resolveFn(ResolveParams{
result, err := resolveFn(ResolveParams{
Source: source,
Args: args,
Info: info,
Context: eCtx.Context,
})

if resolveFnError != nil {
panic(gqlerrors.FormatError(resolveFnError))
if err != nil {
return nil, resultState, gqlerrors.FormatError(err)
}

completed := completeValueCatchingError(eCtx, returnType, fieldASTs, info, result)
return completed, resultState
completed, err := completeValueCatchingError(eCtx, returnType, fieldASTs, info, result)
return completed, resultState, err
}

func completeValueCatchingError(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (completed interface{}) {
// catch panic
defer func() interface{} {
if r := recover(); r != nil {
//send panic upstream
if _, ok := returnType.(*NonNull); ok {
panic(r)
}
if err, ok := r.(gqlerrors.FormattedError); ok {
eCtx.Errors = append(eCtx.Errors, err)
}
return completed
}
return completed
}()

if returnType, ok := returnType.(*NonNull); ok {
completed := completeValue(eCtx, returnType, fieldASTs, info, result)
return completed
}
completed = completeValue(eCtx, returnType, fieldASTs, info, result)
return completed
func completeValueCatchingError(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (interface{}, error) {
return completeValue(eCtx, returnType, fieldASTs, info, result)
}

func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} {
func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (interface{}, error) {

resultVal := reflect.ValueOf(result)
if resultVal.IsValid() && resultVal.Type().Kind() == reflect.Func {
if propertyFn, ok := result.(func() interface{}); ok {
return propertyFn()
return propertyFn(), nil
}
err := gqlerrors.NewFormattedError("Error resolving func. Expected `func() interface{}` signature")
panic(gqlerrors.FormatError(err))
return nil, gqlerrors.FormatError(err)
}

// If field type is NonNull, complete for inner type, and throw field error
// if result is null.
if returnType, ok := returnType.(*NonNull); ok {
completed := completeValue(eCtx, returnType.OfType, fieldASTs, info, result)
completed, err := completeValue(eCtx, returnType.OfType, fieldASTs, info, result)
if err != nil {
return nil, err
}
if completed == nil {
err := NewLocatedError(
fmt.Sprintf("Cannot return null for non-nullable field %v.%v.", info.ParentType, info.FieldName),
FieldASTsToNodeASTs(fieldASTs),
)
panic(gqlerrors.FormatError(err))
return nil, gqlerrors.FormatError(err)
}
return completed
return completed, nil
}

// If result value is null-ish (null, undefined, or NaN) then return null.
if isNullish(result) {
return nil
return nil, nil
}

// If field type is List, complete each item in the list with the inner type
Expand Down Expand Up @@ -639,14 +586,14 @@ func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Fie
fmt.Sprintf(`Cannot complete value of unexpected type "%v."`, returnType),
)
if err != nil {
panic(gqlerrors.FormatError(err))
return nil, gqlerrors.FormatError(err)
}
return nil
return nil, nil
}

// completeAbstractValue completes value of an Abstract type (Union / Interface) by determining the runtime type
// of that value, then completing based on that type.
func completeAbstractValue(eCtx *ExecutionContext, returnType Abstract, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} {
func completeAbstractValue(eCtx *ExecutionContext, returnType Abstract, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (interface{}, error) {

var runtimeType *Object

Expand All @@ -667,21 +614,21 @@ func completeAbstractValue(eCtx *ExecutionContext, returnType Abstract, fieldAST
fmt.Sprintf(`Could not determine runtime type of value "%v" for field %v.%v.`, result, info.ParentType, info.FieldName),
)
if err != nil {
panic(err)
return nil, err
}

if !eCtx.Schema.IsPossibleType(returnType, runtimeType) {
panic(gqlerrors.NewFormattedError(
return nil, gqlerrors.NewFormattedError(
fmt.Sprintf(`Runtime Object type "%v" is not a possible type `+
`for "%v".`, runtimeType, returnType),
))
)
}

return completeObjectValue(eCtx, runtimeType, fieldASTs, info, result)
}

// completeObjectValue complete an Object value by executing all sub-selections.
func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} {
func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (interface{}, error) {

// If there is an isTypeOf predicate function, call it with the
// current result. If isTypeOf returns false, then raise an error rather
Expand All @@ -693,9 +640,9 @@ func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs [
Context: eCtx.Context,
}
if !returnType.IsTypeOf(p) {
panic(gqlerrors.NewFormattedError(
return nil, gqlerrors.NewFormattedError(
fmt.Sprintf(`Expected value of type "%v" but got: %T.`, returnType, result),
))
)
}
}

Expand Down Expand Up @@ -726,21 +673,21 @@ func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs [
}
results := executeFields(executeFieldsParams)

return results.Data
return results.Data, nil

}

// completeLeafValue complete a leaf value (Scalar / Enum) by serializing to a valid value, returning nil if serialization is not possible.
func completeLeafValue(returnType Leaf, result interface{}) interface{} {
func completeLeafValue(returnType Leaf, result interface{}) (interface{}, error) {
serializedResult := returnType.Serialize(result)
if isNullish(serializedResult) {
return nil
return nil, nil
}
return serializedResult
return serializedResult, nil
}

// completeListValue complete a list value by completing each item in the list with the inner type
func completeListValue(eCtx *ExecutionContext, returnType *List, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} {
func completeListValue(eCtx *ExecutionContext, returnType *List, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) (interface{}, error) {
resultVal := reflect.ValueOf(result)
parentTypeName := ""
if info.ParentType != nil {
Expand All @@ -752,17 +699,20 @@ func completeListValue(eCtx *ExecutionContext, returnType *List, fieldASTs []*as
"for field %v.%v.", parentTypeName, info.FieldName),
)
if err != nil {
panic(gqlerrors.FormatError(err))
return nil, gqlerrors.FormatError(err)
}

itemType := returnType.OfType
completedResults := []interface{}{}
for i := 0; i < resultVal.Len(); i++ {
val := resultVal.Index(i).Interface()
completedItem := completeValueCatchingError(eCtx, itemType, fieldASTs, info, val)
completedItem, err := completeValueCatchingError(eCtx, itemType, fieldASTs, info, val)
if err != nil {
return nil, err
}
completedResults = append(completedResults, completedItem)
}
return completedResults
return completedResults, nil
}

// defaultResolveTypeFn If a resolveType function is not given, then a default resolve behavior is
Expand Down

0 comments on commit 66ec6ba

Please sign in to comment.