// Extracts the root type of the operation from the schema. func getOperationRootType(schema Schema, operation ast.Definition, r chan *Result) (objType *Object) { if operation == nil { var result Result err := gqlerrors.NewFormattedError("Can only execute queries and mutations") result.Errors = append(result.Errors, err) r <- &result return objType } switch operation.GetOperation() { case "query": return schema.GetQueryType() case "mutation": mutationType := schema.GetMutationType() if mutationType.Name == "" { var result Result err := gqlerrors.NewFormattedError("Schema is not configured for mutations") result.Errors = append(result.Errors, err) r <- &result return objType } return mutationType default: var result Result err := gqlerrors.NewFormattedError("Can only execute queries and mutations") result.Errors = append(result.Errors, err) r <- &result return objType } }
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 invariant(condition bool, message string) error { if !condition { return gqlerrors.NewFormattedError(message) } return nil }
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 }