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 }
// 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{} { var runtimeType *Object resolveTypeParams := ResolveTypeParams{ Value: result, Info: info, Context: eCtx.Context, } if unionReturnType, ok := returnType.(*Union); ok && unionReturnType.ResolveType != nil { runtimeType = unionReturnType.ResolveType(resolveTypeParams) } else if interfaceReturnType, ok := returnType.(*Interface); ok && interfaceReturnType.ResolveType != nil { runtimeType = interfaceReturnType.ResolveType(resolveTypeParams) } else { runtimeType = defaultResolveTypeFn(resolveTypeParams, returnType) } err := invariant(runtimeType != nil, fmt.Sprintf(`Could not determine runtime type of value "%v" for field %v.%v.`, result, info.ParentType, info.FieldName), ) if err != nil { panic(err) } if !eCtx.Schema.IsPossibleType(returnType, runtimeType) { panic(gqlerrors.NewFormattedError( fmt.Sprintf(`Runtime Object type "%v" is not a possible type `+ `for "%v".`, runtimeType, returnType), )) } return completeObjectValue(eCtx, runtimeType, fieldASTs, info, result) }
func ValidateDocument(schema *Schema, astDoc *ast.Document, rules []ValidationRuleFn) (vr ValidationResult) { if len(rules) == 0 { rules = SpecifiedRules } vr.IsValid = false if schema == nil { vr.Errors = append(vr.Errors, gqlerrors.NewFormattedError("Must provide schema")) return vr } if astDoc == nil { vr.Errors = append(vr.Errors, gqlerrors.NewFormattedError("Must provide document")) return vr } vr.Errors = visitUsingRules(schema, astDoc, rules) if len(vr.Errors) == 0 { vr.IsValid = true } return vr }
// completeObjectValue complete an Object value by executing all sub-selections. func completeObjectValue(eCtx *ExecutionContext, returnType *Object, fieldASTs []*ast.Field, info ResolveInfo, result interface{}) interface{} { // 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 returnType.IsTypeOf != nil { p := IsTypeOfParams{ Value: result, Info: info, Context: eCtx.Context, } if !returnType.IsTypeOf(p) { panic(gqlerrors.NewFormattedError( fmt.Sprintf(`Expected value of type "%v" but got: %T.`, returnType, 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, RuntimeType: returnType, SelectionSet: selectionSet, Fields: subFieldASTs, VisitedFragmentNames: visitedFragmentNames, } subFieldASTs = collectFields(innerParams) } } executeFieldsParams := ExecuteFieldsParams{ ExecutionContext: eCtx, ParentType: returnType, Source: result, Fields: subFieldASTs, } results := executeFields(executeFieldsParams) return results.Data }
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 }
func invariant(condition bool, message string) error { if !condition { return gqlerrors.NewFormattedError(message) } return nil }
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 }