diff --git a/executor.go b/executor.go index 80d6d261..8593e432 100644 --- a/executor.go +++ b/executor.go @@ -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. @@ -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, } } @@ -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 @@ -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 @@ -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 @@ -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 @@ -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), - )) + ) } } @@ -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 { @@ -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