package graphql import ( "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/kinds" ) // TODO: can move TypeInfo to a utils package if there ever is one /** * TypeInfo is a utility class which, given a GraphQL schema, can keep track * of the current field and type definitions at any point in a GraphQL document * AST during a recursive descent by calling `enter(node)` and `leave(node)`. */ type fieldDefFn func(schema *Schema, parentType Type, fieldAST *ast.Field) *FieldDefinition type TypeInfo struct { schema *Schema typeStack []Output parentTypeStack []Composite inputTypeStack []Input fieldDefStack []*FieldDefinition directive *Directive argument *Argument getFieldDef fieldDefFn } type TypeInfoConfig struct { Schema *Schema // NOTE: this experimental optional second parameter is only needed in order // to support non-spec-compliant codebases. You should never need to use it. // It may disappear in the future. FieldDefFn fieldDefFn } func NewTypeInfo(opts *TypeInfoConfig) *TypeInfo { getFieldDef := opts.FieldDefFn if getFieldDef == nil { getFieldDef = DefaultTypeInfoFieldDef } return &TypeInfo{ schema: opts.Schema, getFieldDef: getFieldDef, } } func (ti *TypeInfo) Type() Output { if len(ti.typeStack) > 0 { return ti.typeStack[len(ti.typeStack)-1] } return nil } func (ti *TypeInfo) ParentType() Composite { if len(ti.parentTypeStack) > 0 { return ti.parentTypeStack[len(ti.parentTypeStack)-1] } return nil } func (ti *TypeInfo) InputType() Input { if len(ti.inputTypeStack) > 0 { return ti.inputTypeStack[len(ti.inputTypeStack)-1] } return nil } func (ti *TypeInfo) FieldDef() *FieldDefinition { if len(ti.fieldDefStack) > 0 { return ti.fieldDefStack[len(ti.fieldDefStack)-1] } return nil } func (ti *TypeInfo) Directive() *Directive { return ti.directive } func (ti *TypeInfo) Argument() *Argument { return ti.argument } func (ti *TypeInfo) Enter(node ast.Node) { schema := ti.schema var ttype Type switch node := node.(type) { case *ast.SelectionSet: namedType := GetNamed(ti.Type()) var compositeType Composite if IsCompositeType(namedType) { compositeType, _ = namedType.(Composite) } ti.parentTypeStack = append(ti.parentTypeStack, compositeType) case *ast.Field: parentType := ti.ParentType() var fieldDef *FieldDefinition if parentType != nil { fieldDef = ti.getFieldDef(schema, parentType.(Type), node) } ti.fieldDefStack = append(ti.fieldDefStack, fieldDef) if fieldDef != nil { ti.typeStack = append(ti.typeStack, fieldDef.Type) } else { ti.typeStack = append(ti.typeStack, nil) } case *ast.Directive: nameVal := "" if node.Name != nil { nameVal = node.Name.Value } ti.directive = schema.Directive(nameVal) case *ast.OperationDefinition: if node.Operation == ast.OperationTypeQuery { ttype = schema.QueryType() } else if node.Operation == ast.OperationTypeMutation { ttype = schema.MutationType() } else if node.Operation == ast.OperationTypeSubscription { ttype = schema.SubscriptionType() } ti.typeStack = append(ti.typeStack, ttype) case *ast.InlineFragment: typeConditionAST := node.TypeCondition if typeConditionAST != nil { ttype, _ = typeFromAST(*schema, node.TypeCondition) ti.typeStack = append(ti.typeStack, ttype) } else { ti.typeStack = append(ti.typeStack, ti.Type()) } case *ast.FragmentDefinition: typeConditionAST := node.TypeCondition if typeConditionAST != nil { ttype, _ = typeFromAST(*schema, typeConditionAST) ti.typeStack = append(ti.typeStack, ttype) } else { ti.typeStack = append(ti.typeStack, ti.Type()) } case *ast.VariableDefinition: ttype, _ = typeFromAST(*schema, node.Type) ti.inputTypeStack = append(ti.inputTypeStack, ttype) case *ast.Argument: nameVal := "" if node.Name != nil { nameVal = node.Name.Value } var argType Input var argDef *Argument directive := ti.Directive() fieldDef := ti.FieldDef() if directive != nil { for _, arg := range directive.Args { if arg.Name() == nameVal { argDef = arg } } } else if fieldDef != nil { for _, arg := range fieldDef.Args { if arg.Name() == nameVal { argDef = arg } } } if argDef != nil { argType = argDef.Type } ti.argument = argDef ti.inputTypeStack = append(ti.inputTypeStack, argType) case *ast.ListValue: listType := GetNullable(ti.InputType()) if list, ok := listType.(*List); ok { ti.inputTypeStack = append(ti.inputTypeStack, list.OfType) } else { ti.inputTypeStack = append(ti.inputTypeStack, nil) } case *ast.ObjectField: var fieldType Input objectType := GetNamed(ti.InputType()) if objectType, ok := objectType.(*InputObject); ok { nameVal := "" if node.Name != nil { nameVal = node.Name.Value } if inputField, ok := objectType.Fields()[nameVal]; ok { fieldType = inputField.Type } } ti.inputTypeStack = append(ti.inputTypeStack, fieldType) } } func (ti *TypeInfo) Leave(node ast.Node) { kind := node.GetKind() switch kind { case kinds.SelectionSet: // pop ti.parentTypeStack if len(ti.parentTypeStack) > 0 { _, ti.parentTypeStack = ti.parentTypeStack[len(ti.parentTypeStack)-1], ti.parentTypeStack[:len(ti.parentTypeStack)-1] } case kinds.Field: // pop ti.fieldDefStack if len(ti.fieldDefStack) > 0 { _, ti.fieldDefStack = ti.fieldDefStack[len(ti.fieldDefStack)-1], ti.fieldDefStack[:len(ti.fieldDefStack)-1] } // pop ti.typeStack if len(ti.typeStack) > 0 { _, ti.typeStack = ti.typeStack[len(ti.typeStack)-1], ti.typeStack[:len(ti.typeStack)-1] } case kinds.Directive: ti.directive = nil case kinds.OperationDefinition: fallthrough case kinds.InlineFragment: fallthrough case kinds.FragmentDefinition: // pop ti.typeStack if len(ti.typeStack) > 0 { _, ti.typeStack = ti.typeStack[len(ti.typeStack)-1], ti.typeStack[:len(ti.typeStack)-1] } case kinds.VariableDefinition: // pop ti.inputTypeStack if len(ti.inputTypeStack) > 0 { _, ti.inputTypeStack = ti.inputTypeStack[len(ti.inputTypeStack)-1], ti.inputTypeStack[:len(ti.inputTypeStack)-1] } case kinds.Argument: ti.argument = nil // pop ti.inputTypeStack if len(ti.inputTypeStack) > 0 { _, ti.inputTypeStack = ti.inputTypeStack[len(ti.inputTypeStack)-1], ti.inputTypeStack[:len(ti.inputTypeStack)-1] } case kinds.ListValue: fallthrough case kinds.ObjectField: // pop ti.inputTypeStack if len(ti.inputTypeStack) > 0 { _, ti.inputTypeStack = ti.inputTypeStack[len(ti.inputTypeStack)-1], ti.inputTypeStack[:len(ti.inputTypeStack)-1] } } } // DefaultTypeInfoFieldDef Not exactly the same as the executor's definition of FieldDef, in this // statically evaluated environment we do not always have an Object type, // and need to handle Interface and Union types. func DefaultTypeInfoFieldDef(schema *Schema, parentType Type, fieldAST *ast.Field) *FieldDefinition { name := "" if fieldAST.Name != nil { name = fieldAST.Name.Value } if name == SchemaMetaFieldDef.Name && schema.QueryType() == parentType { return SchemaMetaFieldDef } if name == TypeMetaFieldDef.Name && schema.QueryType() == parentType { return TypeMetaFieldDef } if name == TypeNameMetaFieldDef.Name && parentType != nil { if t, ok := parentType.(*Object); ok && t != nil { return TypeNameMetaFieldDef } if t, ok := parentType.(*Interface); ok && t != nil { return TypeNameMetaFieldDef } if t, ok := parentType.(*Union); ok && t != nil { return TypeNameMetaFieldDef } } if parentType, ok := parentType.(*Object); ok && parentType != nil { field, _ := parentType.Fields()[name] return field } if parentType, ok := parentType.(*Interface); ok && parentType != nil { field, _ := parentType.Fields()[name] return field } return nil }