func Execute(p ExecuteParams, resultChan chan *Result) { var errors []gqlerrors.FormattedError var result Result params := BuildExecutionCtxParams{ Schema: p.Schema, Root: p.Root, AST: p.AST, OperationName: p.OperationName, Args: p.Args, Errors: errors, Result: &result, ResultChan: resultChan, } exeContext := buildExecutionContext(params) if result.HasErrors() { return } defer func() { if r := recover(); r != nil { var err error if r, ok := r.(error); ok { err = gqlerrors.FormatError(r) } exeContext.Errors = append(exeContext.Errors, gqlerrors.FormatError(err)) result.Errors = exeContext.Errors resultChan <- &result } }() eOperationParams := ExecuteOperationParams{ ExecutionContext: exeContext, Root: p.Root, Operation: exeContext.Operation, } executeOperation(eOperationParams, resultChan) }
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) resultVal := reflect.ValueOf(completed) if resultVal.IsValid() && resultVal.Type().Kind() == reflect.Func { if propertyFn, ok := completed.(func() interface{}); ok { return propertyFn() } err := gqlerrors.NewFormattedError("Error resolving func. Expected `func() interface{}` signature") panic(gqlerrors.FormatError(err)) } return completed }
func buildExecutionContext(p BuildExecutionCtxParams) *ExecutionContext { eCtx := &ExecutionContext{} operations := map[string]ast.Definition{} fragments := map[string]ast.Definition{} for _, statement := range p.AST.Definitions { switch stm := statement.(type) { case *ast.OperationDefinition: key := "" if stm.GetName() != nil && stm.GetName().Value != "" { key = stm.GetName().Value } operations[key] = stm case *ast.FragmentDefinition: key := "" if stm.GetName() != nil && stm.GetName().Value != "" { key = stm.GetName().Value } fragments[key] = stm default: err := gqlerrors.NewFormattedError( fmt.Sprintf("GraphQL cannot execute a request containing a %v", statement.GetKind()), ) p.Result.Errors = append(p.Result.Errors, err) p.ResultChan <- p.Result return eCtx } } if (p.OperationName == "") && (len(operations) != 1) { err := gqlerrors.NewFormattedError("Must provide operation name if query contains multiple operations.") p.Result.Errors = append(p.Result.Errors, err) p.ResultChan <- p.Result return eCtx } opName := p.OperationName if opName == "" { // get first opName for k, _ := range operations { opName = k break } } operation, found := operations[opName] if !found { err := gqlerrors.NewFormattedError(fmt.Sprintf(`Unknown operation named "%v".`, opName)) p.Result.Errors = append(p.Result.Errors, err) p.ResultChan <- p.Result return eCtx } variableValues, err := getVariableValues(p.Schema, operation.GetVariableDefinitions(), p.Args) if err != nil { p.Result.Errors = append(p.Result.Errors, gqlerrors.FormatError(err)) p.ResultChan <- p.Result return eCtx } eCtx.Schema = p.Schema eCtx.Fragments = fragments eCtx.Root = p.Root eCtx.Operation = operation eCtx.VariableValues = variableValues eCtx.Errors = p.Errors return eCtx }
func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { // TODO: explore resolving go-routines in completeValue resultVal := reflect.ValueOf(result) if resultVal.IsValid() && resultVal.Type().Kind() == reflect.Func { if propertyFn, ok := result.(func() interface{}); ok { return propertyFn() } err := gqlerrors.NewFormattedError("Error resolving func. Expected `func() interface{}` signature") panic(gqlerrors.FormatError(err)) } if returnType, ok := returnType.(*NonNull); ok { completed := completeValue(eCtx, returnType.OfType, fieldASTs, info, result) 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 completed } if isNullish(result) { return nil } // If field type is List, complete each item in the list with the inner type if returnType, ok := returnType.(*List); ok { resultVal := reflect.ValueOf(result) err := invariant( resultVal.IsValid() && resultVal.Type().Kind() == reflect.Slice, "User Error: expected iterable, but did not find one.", ) if err != nil { panic(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) completedResults = append(completedResults, completedItem) } return completedResults } // If field type is Scalar or Enum, serialize to a valid value, returning // null if serialization is not possible. if returnType, ok := returnType.(*Scalar); ok { err := invariant(returnType.Serialize != nil, "Missing serialize method on type") if err != nil { panic(gqlerrors.FormatError(err)) } serializedResult := returnType.Serialize(result) if isNullish(serializedResult) { return nil } return serializedResult } if returnType, ok := returnType.(*Enum); ok { err := invariant(returnType.Serialize != nil, "Missing serialize method on type") if err != nil { panic(gqlerrors.FormatError(err)) } serializedResult := returnType.Serialize(result) if isNullish(serializedResult) { return nil } return serializedResult } // ast.Field type must be Object, Interface or Union and expect sub-selections. var objectType *Object switch returnType := returnType.(type) { case *Object: objectType = returnType case Abstract: objectType = returnType.GetObjectType(result, info) if objectType != nil && !returnType.IsPossibleType(objectType) { panic(gqlerrors.NewFormattedError( fmt.Sprintf(`Runtime Object type "%v" is not a possible type `+ `for "%v".`, objectType, returnType), )) } } if objectType == nil { return nil } // If there is an isTypeOf predicate function, call it with the // current result. If isTypeOf returns false, then raise an error rather // than continuing execution. if objectType.IsTypeOf != nil && !objectType.IsTypeOf(result, info) { panic(gqlerrors.NewFormattedError( fmt.Sprintf(`Expected value of type "%v" but got: %T.`, objectType, result), )) } // Collect sub-fields to execute to complete this value. subFieldASTs := map[string][]*ast.Field{} visitedFragmentNames := map[string]bool{} for _, fieldAST := range fieldASTs { if fieldAST == nil { continue } selectionSet := fieldAST.SelectionSet if selectionSet != nil { innerParams := CollectFieldsParams{ ExeContext: eCtx, OperationType: objectType, SelectionSet: selectionSet, Fields: subFieldASTs, VisitedFragmentNames: visitedFragmentNames, } subFieldASTs = collectFields(innerParams) } } executeFieldsParams := ExecuteFieldsParams{ ExecutionContext: eCtx, ParentType: objectType, Source: result, Fields: subFieldASTs, } results := executeFields(executeFieldsParams) return results.Data }
/** * Resolves the field on the given source object. In particular, this * 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 }() fieldAST := fieldASTs[0] fieldName := "" if fieldAST.Name != nil { fieldName = fieldAST.Name.Value } fieldDef := getFieldDef(eCtx.Schema, parentType, fieldName) if fieldDef == nil { resultState.hasNoFieldDefs = true return nil, resultState } returnType = fieldDef.Type resolveFn := fieldDef.Resolve if resolveFn == nil { resolveFn = defaultResolveFn } // Build a map of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. // TODO: find a way to memoize, in case this field is within a List type. args, _ := getArgumentValues(fieldDef.Args, fieldAST.Arguments, eCtx.VariableValues) // The resolve function's optional third argument is a collection of // information about the current execution state. info := ResolveInfo{ FieldName: fieldName, FieldASTs: fieldASTs, ReturnType: returnType, ParentType: parentType, Schema: eCtx.Schema, Fragments: eCtx.Fragments, RootValue: eCtx.Root, Operation: eCtx.Operation, VariableValues: eCtx.VariableValues, } // TODO: If an error occurs while calling the field `resolve` function, ensure that // it is wrapped as a Error with locations. Log this error and return // null if allowed, otherwise throw the error so the parent field can handle // it. result = resolveFn(GQLFRParams{ Source: source, Args: args, Info: info, }) completed := completeValueCatchingError(eCtx, returnType, fieldASTs, info, result) return completed, resultState }