package graphql
import (
"encoding/json"
"fmt"
"math"
"reflect"
"strings"
"github.com/graphql-go/graphql/gqlerrors"
"github.com/graphql-go/graphql/language/ast"
"github.com/graphql-go/graphql/language/kinds"
"github.com/graphql-go/graphql/language/printer"
"sort"
)
// Prepares an object map of variableValues of the correct type based on the
// provided variable definitions and arbitrary input. If the input cannot be
// parsed to match the variable definitions, a GraphQLError will be returned.
func getVariableValues(schema Schema, definitionASTs []*ast.VariableDefinition, inputs map[string]interface{}) (map[string]interface{}, error) {
values := map[string]interface{}{}
for _, defAST := range definitionASTs {
if defAST == nil || defAST.Variable == nil || defAST.Variable.Name == nil {
continue
}
varName := defAST.Variable.Name.Value
varValue, err := getVariableValue(schema, defAST, inputs[varName])
if err != nil {
return values, err
}
values[varName] = varValue
}
return values, nil
}
// Prepares an object map of argument values given a list of argument
// definitions and list of argument AST nodes.
func getArgumentValues(argDefs []*Argument, argASTs []*ast.Argument, variableVariables map[string]interface{}) (map[string]interface{}, error) {
argASTMap := map[string]*ast.Argument{}
for _, argAST := range argASTs {
if argAST.Name != nil {
argASTMap[argAST.Name.Value] = argAST
}
}
results := map[string]interface{}{}
for _, argDef := range argDefs {
name := argDef.PrivateName
var valueAST ast.Value
if argAST, ok := argASTMap[name]; ok {
valueAST = argAST.Value
}
value := valueFromAST(valueAST, argDef.Type, variableVariables)
if isNullish(value) {
value = argDef.DefaultValue
}
if !isNullish(value) {
results[name] = value
}
}
return results, nil
}
// Given a variable definition, and any value of input, return a value which
// adheres to the variable definition, or throw an error.
func getVariableValue(schema Schema, definitionAST *ast.VariableDefinition, input interface{}) (interface{}, error) {
ttype, err := typeFromAST(schema, definitionAST.Type)
if err != nil {
return nil, err
}
variable := definitionAST.Variable
if ttype == nil || !IsInputType(ttype) {
return "", gqlerrors.NewError(
fmt.Sprintf(`Variable "$%v" expected value of type `+
`"%v" which cannot be used as an input type.`, variable.Name.Value, printer.Print(definitionAST.Type)),
[]ast.Node{definitionAST},
"",
nil,
[]int{},
nil,
)
}
isValid, messages := isValidInputValue(input, ttype)
if isValid {
if isNullish(input) {
defaultValue := definitionAST.DefaultValue
if defaultValue != nil {
variables := map[string]interface{}{}
val := valueFromAST(defaultValue, ttype, variables)
return val, nil
}
}
return coerceValue(ttype, input), nil
}
if isNullish(input) {
return "", gqlerrors.NewError(
fmt.Sprintf(`Variable "$%v" of required type `+
`"%v" was not provided.`, variable.Name.Value, printer.Print(definitionAST.Type)),
[]ast.Node{definitionAST},
"",
nil,
[]int{},
nil,
)
}
// convert input interface into string for error message
inputStr := ""
b, err := json.Marshal(input)
if err == nil {
inputStr = string(b)
}
messagesStr := ""
if len(messages) > 0 {
messagesStr = "\n" + strings.Join(messages, "\n")
}
return "", gqlerrors.NewError(
fmt.Sprintf(`Variable "$%v" got invalid value `+
`%v.%v`, variable.Name.Value, inputStr, messagesStr),
[]ast.Node{definitionAST},
"",
nil,
[]int{},
nil,
)
}
// Given a type and any value, return a runtime value coerced to match the type.
func coerceValue(ttype Input, value interface{}) interface{} {
if ttype, ok := ttype.(*NonNull); ok {
return coerceValue(ttype.OfType, value)
}
if isNullish(value) {
return nil
}
if ttype, ok := ttype.(*List); ok {
itemType := ttype.OfType
valType := reflect.ValueOf(value)
if valType.Kind() == reflect.Slice {
values := []interface{}{}
for i := 0; i < valType.Len(); i++ {
val := valType.Index(i).Interface()
v := coerceValue(itemType, val)
values = append(values, v)
}
return values
}
val := coerceValue(itemType, value)
return []interface{}{val}
}
if ttype, ok := ttype.(*InputObject); ok {
valueMap, ok := value.(map[string]interface{})
if !ok {
valueMap = map[string]interface{}{}
}
obj := map[string]interface{}{}
for fieldName, field := range ttype.Fields() {
value, _ := valueMap[fieldName]
fieldValue := coerceValue(field.Type, value)
if isNullish(fieldValue) {
fieldValue = field.DefaultValue
}
if !isNullish(fieldValue) {
obj[fieldName] = fieldValue
}
}
return obj
}
switch ttype := ttype.(type) {
case *Scalar:
parsed := ttype.ParseValue(value)
if !isNullish(parsed) {
return parsed
}
case *Enum:
parsed := ttype.ParseValue(value)
if !isNullish(parsed) {
return parsed
}
}
return nil
}
// graphql-js/src/utilities.js`
// TODO: figure out where to organize utils
// TODO: change to *Schema
func typeFromAST(schema Schema, inputTypeAST ast.Type) (Type, error) {
switch inputTypeAST := inputTypeAST.(type) {
case *ast.List:
innerType, err := typeFromAST(schema, inputTypeAST.Type)
if err != nil {
return nil, err
}
return NewList(innerType), nil
case *ast.NonNull:
innerType, err := typeFromAST(schema, inputTypeAST.Type)
if err != nil {
return nil, err
}
return NewNonNull(innerType), nil
case *ast.Named:
nameValue := ""
if inputTypeAST.Name != nil {
nameValue = inputTypeAST.Name.Value
}
ttype := schema.Type(nameValue)
return ttype, nil
default:
return nil, invariant(inputTypeAST.GetKind() == kinds.Named, "Must be a named type.")
}
}
// isValidInputValue alias isValidJSValue
// Given a value and a GraphQL type, determine if the value will be
// accepted for that type. This is primarily useful for validating the
// runtime values of query variables.
func isValidInputValue(value interface{}, ttype Input) (bool, []string) {
if ttype, ok := ttype.(*NonNull); ok {
if isNullish(value) {
if ttype.OfType.Name() != "" {
return false, []string{fmt.Sprintf(`Expected "%v!", found null.`, ttype.OfType.Name())}
}
return false, []string{"Expected non-null value, found null."}
}
return isValidInputValue(value, ttype.OfType)
}
if isNullish(value) {
return true, nil
}
switch ttype := ttype.(type) {
case *List:
itemType := ttype.OfType
valType := reflect.ValueOf(value)
if valType.Kind() == reflect.Ptr {
valType = valType.Elem()
}
if valType.Kind() == reflect.Slice {
messagesReduce := []string{}
for i := 0; i < valType.Len(); i++ {
val := valType.Index(i).Interface()
_, messages := isValidInputValue(val, itemType)
for idx, message := range messages {
messagesReduce = append(messagesReduce, fmt.Sprintf(`In element #%v: %v`, idx+1, message))
}
}
return (len(messagesReduce) == 0), messagesReduce
}
return isValidInputValue(value, itemType)
case *InputObject:
messagesReduce := []string{}
valueMap, ok := value.(map[string]interface{})
if !ok {
return false, []string{fmt.Sprintf(`Expected "%v", found not an object.`, ttype.Name())}
}
fields := ttype.Fields()
// to ensure stable order of field evaluation
fieldNames := []string{}
valueMapFieldNames := []string{}
for fieldName := range fields {
fieldNames = append(fieldNames, fieldName)
}
sort.Strings(fieldNames)
for fieldName := range valueMap {
valueMapFieldNames = append(valueMapFieldNames, fieldName)
}
sort.Strings(valueMapFieldNames)
// Ensure every provided field is defined.
for _, fieldName := range valueMapFieldNames {
if _, ok := fields[fieldName]; !ok {
messagesReduce = append(messagesReduce, fmt.Sprintf(`In field "%v": Unknown field.`, fieldName))
}
}
// Ensure every defined field is valid.
for _, fieldName := range fieldNames {
_, messages := isValidInputValue(valueMap[fieldName], fields[fieldName].Type)
if messages != nil {
for _, message := range messages {
messagesReduce = append(messagesReduce, fmt.Sprintf(`In field "%v": %v`, fieldName, message))
}
}
}
return (len(messagesReduce) == 0), messagesReduce
}
switch ttype := ttype.(type) {
case *Scalar:
parsedVal := ttype.ParseValue(value)
if isNullish(parsedVal) {
return false, []string{fmt.Sprintf(`Expected type "%v", found "%v".`, ttype.Name(), value)}
}
return true, nil
case *Enum:
parsedVal := ttype.ParseValue(value)
if isNullish(parsedVal) {
return false, []string{fmt.Sprintf(`Expected type "%v", found "%v".`, ttype.Name(), value)}
}
return true, nil
}
return true, nil
}
// Returns true if a value is null, undefined, or NaN.
func isNullish(value interface{}) bool {
if value, ok := value.(*string); ok {
if value == nil {
return true
}
return *value == ""
}
if value, ok := value.(int); ok {
return math.IsNaN(float64(value))
}
if value, ok := value.(*int); ok {
if value == nil {
return true
}
return math.IsNaN(float64(*value))
}
if value, ok := value.(float32); ok {
return math.IsNaN(float64(value))
}
if value, ok := value.(*float32); ok {
if value == nil {
return true
}
return math.IsNaN(float64(*value))
}
if value, ok := value.(float64); ok {
return math.IsNaN(value)
}
if value, ok := value.(*float64); ok {
if value == nil {
return true
}
return math.IsNaN(*value)
}
return value == nil
}
/**
* Produces a value given a GraphQL Value AST.
*
* A GraphQL type must be provided, which will be used to interpret different
* GraphQL Value literals.
*
* | GraphQL Value | JSON Value |
* | -------------------- | ------------- |
* | Input Object | Object |
* | List | Array |
* | Boolean | Boolean |
* | String / Enum Value | String |
* | Int / Float | Number |
*
*/
func valueFromAST(valueAST ast.Value, ttype Input, variables map[string]interface{}) interface{} {
if ttype, ok := ttype.(*NonNull); ok {
val := valueFromAST(valueAST, ttype.OfType, variables)
return val
}
if valueAST == nil {
return nil
}
if valueAST, ok := valueAST.(*ast.Variable); ok && valueAST.Kind == kinds.Variable {
if valueAST.Name == nil {
return nil
}
if variables == nil {
return nil
}
variableName := valueAST.Name.Value
variableVal, ok := variables[variableName]
if !ok {
return nil
}
// Note: we're not doing any checking that this variable is correct. We're
// assuming that this query has been validated and the variable usage here
// is of the correct type.
return variableVal
}
if ttype, ok := ttype.(*List); ok {
itemType := ttype.OfType
if valueAST, ok := valueAST.(*ast.ListValue); ok && valueAST.Kind == kinds.ListValue {
values := []interface{}{}
for _, itemAST := range valueAST.Values {
v := valueFromAST(itemAST, itemType, variables)
values = append(values, v)
}
return values
}
v := valueFromAST(valueAST, itemType, variables)
return []interface{}{v}
}
if ttype, ok := ttype.(*InputObject); ok {
valueAST, ok := valueAST.(*ast.ObjectValue)
if !ok {
return nil
}
fieldASTs := map[string]*ast.ObjectField{}
for _, fieldAST := range valueAST.Fields {
if fieldAST.Name == nil {
continue
}
fieldName := fieldAST.Name.Value
fieldASTs[fieldName] = fieldAST
}
obj := map[string]interface{}{}
for fieldName, field := range ttype.Fields() {
fieldAST, ok := fieldASTs[fieldName]
fieldValue := field.DefaultValue
if !ok || fieldAST == nil {
if fieldValue == nil {
continue
}
} else {
fieldValue = valueFromAST(fieldAST.Value, field.Type, variables)
}
if isNullish(fieldValue) {
fieldValue = field.DefaultValue
}
if !isNullish(fieldValue) {
obj[fieldName] = fieldValue
}
}
return obj
}
switch ttype := ttype.(type) {
case *Scalar:
parsed := ttype.ParseLiteral(valueAST)
if !isNullish(parsed) {
return parsed
}
case *Enum:
parsed := ttype.ParseLiteral(valueAST)
if !isNullish(parsed) {
return parsed
}
}
return nil
}
func invariant(condition bool, message string) error {
if !condition {
return gqlerrors.NewFormattedError(message)
}
return nil
}
func invariantf(condition bool, format string, a ...interface{}) error {
if !condition {
return gqlerrors.NewFormattedError(fmt.Sprintf(format, a...))
}
return nil
}