func Execute(p ExecuteParams) (result *Result) { result = &Result{} exeContext, err := buildExecutionContext(BuildExecutionCtxParams{ Schema: p.Schema, Root: p.Root, AST: p.AST, OperationName: p.OperationName, Args: p.Args, Errors: nil, Result: result, }) if err != nil { result.Errors = append(result.Errors, gqlerrors.FormatError(err)) 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 } }() return executeOperation(ExecuteOperationParams{ ExecutionContext: exeContext, Root: p.Root, Operation: exeContext.Operation, }) }
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 }
// 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{} { resultVal := reflect.ValueOf(result) parentTypeName := "" if info.ParentType != nil { parentTypeName = info.ParentType.Name() } err := invariant( resultVal.IsValid() && resultVal.Type().Kind() == reflect.Slice, fmt.Sprintf("User Error: expected iterable, but did not find one "+ "for field %v.%v.", parentTypeName, info.FieldName), ) 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 }
func (ctx *ValidationContext) ReportError(err error) { formattedErr := gqlerrors.FormatError(err) ctx.errors = append(ctx.errors, formattedErr) }
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 }
func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { 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 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) 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 result value is null-ish (null, undefined, or NaN) then return null. 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 { return completeListValue(eCtx, returnType, fieldASTs, info, result) } // If field type is a leaf type, Scalar or Enum, serialize to a valid value, // returning null if serialization is not possible. if returnType, ok := returnType.(*Scalar); ok { return completeLeafValue(returnType, result) } if returnType, ok := returnType.(*Enum); ok { return completeLeafValue(returnType, result) } // If field type is an abstract type, Interface or Union, determine the // runtime Object type and complete for that type. if returnType, ok := returnType.(*Union); ok { return completeAbstractValue(eCtx, returnType, fieldASTs, info, result) } if returnType, ok := returnType.(*Interface); ok { return completeAbstractValue(eCtx, returnType, fieldASTs, info, result) } // If field type is Object, execute and complete all sub-selections. if returnType, ok := returnType.(*Object); ok { return completeObjectValue(eCtx, returnType, fieldASTs, info, result) } // Not reachable. All possible output types have been considered. err := invariant(false, fmt.Sprintf(`Cannot complete value of unexpected type "%v."`, returnType), ) if err != nil { panic(gqlerrors.FormatError(err)) } return nil }
// 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) 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, } var resolveFnError error result, resolveFnError = resolveFn(ResolveParams{ Source: source, Args: args, Info: info, Context: eCtx.Context, }) if resolveFnError != nil { panic(gqlerrors.FormatError(resolveFnError)) } completed := completeValueCatchingError(eCtx, returnType, fieldASTs, info, result) return completed, resultState }
func visitUsingRules(schema *Schema, astDoc *ast.Document, rules []ValidationRuleFn) (errors []gqlerrors.FormattedError) { typeInfo := NewTypeInfo(schema) context := NewValidationContext(schema, astDoc, typeInfo) var visitInstance func(astNode ast.Node, instance *ValidationRuleInstance) visitInstance = func(astNode ast.Node, instance *ValidationRuleInstance) { visitor.Visit(astNode, &visitor.VisitorOptions{ Enter: func(p visitor.VisitFuncParams) (string, interface{}) { var action = visitor.ActionNoChange var result interface{} switch node := p.Node.(type) { case ast.Node: // Collect type information about the current position in the AST. typeInfo.Enter(node) // Do not visit top level fragment definitions if this instance will // visit those fragments inline because it // provided `visitSpreadFragments`. kind := node.GetKind() if kind == kinds.FragmentDefinition && p.Key != nil && instance.VisitSpreadFragments == true { return visitor.ActionSkip, nil } // Get the visitor function from the validation instance, and if it // exists, call it with the visitor arguments. enterFn := visitor.GetVisitFn(instance.VisitorOpts, false, kind) if enterFn != nil { action, result = enterFn(p) } // If the visitor returned an error, log it and do not visit any // deeper nodes. if err, ok := result.(error); ok && err != nil { errors = append(errors, gqlerrors.FormatError(err)) action = visitor.ActionSkip } if err, ok := result.([]error); ok && err != nil { errors = append(errors, gqlerrors.FormatErrors(err...)...) action = visitor.ActionSkip } // If any validation instances provide the flag `visitSpreadFragments` // and this node is a fragment spread, visit the fragment definition // from this point. if action == visitor.ActionNoChange && result == nil && instance.VisitSpreadFragments == true && kind == kinds.FragmentSpread { node, _ := node.(*ast.FragmentSpread) name := node.Name nameVal := "" if name != nil { nameVal = name.Value } fragment := context.Fragment(nameVal) if fragment != nil { visitInstance(fragment, instance) } } // If the result is "false" (ie action === Action.Skip), we're not visiting any descendent nodes, // but need to update typeInfo. if action == visitor.ActionSkip { typeInfo.Leave(node) } } return action, result }, Leave: func(p visitor.VisitFuncParams) (string, interface{}) { var action = visitor.ActionNoChange var result interface{} switch node := p.Node.(type) { case ast.Node: kind := node.GetKind() // Get the visitor function from the validation instance, and if it // exists, call it with the visitor arguments. leaveFn := visitor.GetVisitFn(instance.VisitorOpts, true, kind) if leaveFn != nil { action, result = leaveFn(p) } // If the visitor returned an error, log it and do not visit any // deeper nodes. if err, ok := result.(error); ok && err != nil { errors = append(errors, gqlerrors.FormatError(err)) action = visitor.ActionSkip } if err, ok := result.([]error); ok && err != nil { errors = append(errors, gqlerrors.FormatErrors(err...)...) action = visitor.ActionSkip } // Update typeInfo. typeInfo.Leave(node) } return action, result }, }, nil) } instances := []*ValidationRuleInstance{} for _, rule := range rules { instance := rule(context) instances = append(instances, instance) } for _, instance := range instances { visitInstance(astDoc, instance) } return errors }