Example #1
0
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,
	})
}
Example #2
0
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
}
Example #3
0
// 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
}
Example #4
0
func (ctx *ValidationContext) ReportError(err error) {
	formattedErr := gqlerrors.FormatError(err)
	ctx.errors = append(ctx.errors, formattedErr)
}
Example #5
0
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

}
Example #6
0
/**
 * 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
}
Example #7
0
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
}
Example #8
0
// 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
}
Example #9
0
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
}