go-ethereum/vendor/github.com/graph-gophers/graphql-go/internal/schema/schema.go

571 lines
15 KiB
Go
Raw Normal View History

GraphQL master FF for review (#18445) * Initial work on a graphql API * Added receipts, and more transaction fields. * Finish receipts, add logs * Add transactionCount to block * Add types and . * Update Block type to be compatible with ethql * Rename nonce to transactionCount in Account, to be compatible with ethql * Update transaction, receipt and log to match ethql * Add query operator, for a range of blocks * Added ommerCount to Block * Add transactionAt and ommerAt to Block * Added sendRawTransaction mutation * Add Call and EstimateGas to graphQL API * Refactored to use hexutil.Bytes instead of HexBytes * Replace BigNum with hexutil.Big * Refactor call and estimateGas to use ethapi struct type * Replace ethgraphql.Address with common.Address * Replace ethgraphql.Hash with common.Hash * Converted most quantities to Long instead of Int * Add support for logs * Fix bug in runFilter * Restructured Transaction to work primarily with headers, so uncle data is reported properly * Add gasPrice API * Add protocolVersion API * Add syncing API * Moved schema into its own source file * Move some single use args types into anonymous structs * Add doc-comments * Fixed backend fetching to use context * Added (very) basic tests * Add documentation to the graphql schema * Fix reversion for formatting of big numbers * Correct spelling error * s/BigInt/Long/ * Update common/types.go * Fixes in response to review * Fix lint error * Updated calls on private functions * Fix typo in graphql.go * Rollback ethapi breaking changes for graphql support Co-Authored-By: Arachnid <arachnid@notdot.net>
2019-01-21 08:38:13 -06:00
package schema
import (
"fmt"
"text/scanner"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/internal/common"
)
// Schema represents a GraphQL service's collective type system capabilities.
// A schema is defined in terms of the types and directives it supports as well as the root
// operation types for each kind of operation: `query`, `mutation`, and `subscription`.
//
// For a more formal definition, read the relevant section in the specification:
//
// http://facebook.github.io/graphql/draft/#sec-Schema
type Schema struct {
// EntryPoints determines the place in the type system where `query`, `mutation`, and
// `subscription` operations begin.
//
// http://facebook.github.io/graphql/draft/#sec-Root-Operation-Types
//
// NOTE: The specification refers to this concept as "Root Operation Types".
// TODO: Rename the `EntryPoints` field to `RootOperationTypes` to align with spec terminology.
EntryPoints map[string]NamedType
// Types are the fundamental unit of any GraphQL schema.
// There are six kinds of named types, and two wrapping types.
//
// http://facebook.github.io/graphql/draft/#sec-Types
Types map[string]NamedType
// TODO: Type extensions?
// http://facebook.github.io/graphql/draft/#sec-Type-Extensions
// Directives are used to annotate various parts of a GraphQL document as an indicator that they
// should be evaluated differently by a validator, executor, or client tool such as a code
// generator.
//
// http://facebook.github.io/graphql/draft/#sec-Type-System.Directives
Directives map[string]*DirectiveDecl
entryPointNames map[string]string
objects []*Object
unions []*Union
enums []*Enum
}
// Resolve a named type in the schema by its name.
func (s *Schema) Resolve(name string) common.Type {
return s.Types[name]
}
// NamedType represents a type with a name.
//
// http://facebook.github.io/graphql/draft/#NamedType
type NamedType interface {
common.Type
TypeName() string
Description() string
}
// Scalar types represent primitive leaf values (e.g. a string or an integer) in a GraphQL type
// system.
//
// GraphQL responses take the form of a hierarchical tree; the leaves on these trees are GraphQL
// scalars.
//
// http://facebook.github.io/graphql/draft/#sec-Scalars
type Scalar struct {
Name string
Desc string
// TODO: Add a list of directives?
}
// Object types represent a list of named fields, each of which yield a value of a specific type.
//
// GraphQL queries are hierarchical and composed, describing a tree of information.
// While Scalar types describe the leaf values of these hierarchical types, Objects describe the
// intermediate levels.
//
// http://facebook.github.io/graphql/draft/#sec-Objects
type Object struct {
Name string
Interfaces []*Interface
Fields FieldList
Desc string
// TODO: Add a list of directives?
interfaceNames []string
}
// Interface types represent a list of named fields and their arguments.
//
// GraphQL objects can then implement these interfaces which requires that the object type will
// define all fields defined by those interfaces.
//
// http://facebook.github.io/graphql/draft/#sec-Interfaces
type Interface struct {
Name string
PossibleTypes []*Object
Fields FieldList // NOTE: the spec refers to this as `FieldsDefinition`.
Desc string
// TODO: Add a list of directives?
}
// Union types represent objects that could be one of a list of GraphQL object types, but provides no
// guaranteed fields between those types.
//
// They also differ from interfaces in that object types declare what interfaces they implement, but
// are not aware of what unions contain them.
//
// http://facebook.github.io/graphql/draft/#sec-Unions
type Union struct {
Name string
PossibleTypes []*Object // NOTE: the spec refers to this as `UnionMemberTypes`.
Desc string
// TODO: Add a list of directives?
typeNames []string
}
// Enum types describe a set of possible values.
//
// Like scalar types, Enum types also represent leaf values in a GraphQL type system.
//
// http://facebook.github.io/graphql/draft/#sec-Enums
type Enum struct {
Name string
Values []*EnumValue // NOTE: the spec refers to this as `EnumValuesDefinition`.
Desc string
// TODO: Add a list of directives?
}
// EnumValue types are unique values that may be serialized as a string: the name of the
// represented value.
//
// http://facebook.github.io/graphql/draft/#EnumValueDefinition
type EnumValue struct {
Name string
Directives common.DirectiveList
Desc string
// TODO: Add a list of directives?
}
// InputObject types define a set of input fields; the input fields are either scalars, enums, or
// other input objects.
//
// This allows arguments to accept arbitrarily complex structs.
//
// http://facebook.github.io/graphql/draft/#sec-Input-Objects
type InputObject struct {
Name string
Desc string
Values common.InputValueList
// TODO: Add a list of directives?
}
// FieldsList is a list of an Object's Fields.
//
// http://facebook.github.io/graphql/draft/#FieldsDefinition
type FieldList []*Field
// Get iterates over the field list, returning a pointer-to-Field when the field name matches the
// provided `name` argument.
// Returns nil when no field was found by that name.
func (l FieldList) Get(name string) *Field {
for _, f := range l {
if f.Name == name {
return f
}
}
return nil
}
// Names returns a string slice of the field names in the FieldList.
func (l FieldList) Names() []string {
names := make([]string, len(l))
for i, f := range l {
names[i] = f.Name
}
return names
}
// http://facebook.github.io/graphql/draft/#sec-Type-System.Directives
type DirectiveDecl struct {
Name string
Desc string
Locs []string
Args common.InputValueList
}
func (*Scalar) Kind() string { return "SCALAR" }
func (*Object) Kind() string { return "OBJECT" }
func (*Interface) Kind() string { return "INTERFACE" }
func (*Union) Kind() string { return "UNION" }
func (*Enum) Kind() string { return "ENUM" }
func (*InputObject) Kind() string { return "INPUT_OBJECT" }
func (t *Scalar) String() string { return t.Name }
func (t *Object) String() string { return t.Name }
func (t *Interface) String() string { return t.Name }
func (t *Union) String() string { return t.Name }
func (t *Enum) String() string { return t.Name }
func (t *InputObject) String() string { return t.Name }
func (t *Scalar) TypeName() string { return t.Name }
func (t *Object) TypeName() string { return t.Name }
func (t *Interface) TypeName() string { return t.Name }
func (t *Union) TypeName() string { return t.Name }
func (t *Enum) TypeName() string { return t.Name }
func (t *InputObject) TypeName() string { return t.Name }
func (t *Scalar) Description() string { return t.Desc }
func (t *Object) Description() string { return t.Desc }
func (t *Interface) Description() string { return t.Desc }
func (t *Union) Description() string { return t.Desc }
func (t *Enum) Description() string { return t.Desc }
func (t *InputObject) Description() string { return t.Desc }
// Field is a conceptual function which yields values.
// http://facebook.github.io/graphql/draft/#FieldDefinition
type Field struct {
Name string
Args common.InputValueList // NOTE: the spec refers to this as `ArgumentsDefinition`.
Type common.Type
Directives common.DirectiveList
Desc string
}
// New initializes an instance of Schema.
func New() *Schema {
s := &Schema{
entryPointNames: make(map[string]string),
Types: make(map[string]NamedType),
Directives: make(map[string]*DirectiveDecl),
}
for n, t := range Meta.Types {
s.Types[n] = t
}
for n, d := range Meta.Directives {
s.Directives[n] = d
}
return s
}
// Parse the schema string.
func (s *Schema) Parse(schemaString string) error {
l := common.NewLexer(schemaString)
err := l.CatchSyntaxError(func() { parseSchema(s, l) })
if err != nil {
return err
}
for _, t := range s.Types {
if err := resolveNamedType(s, t); err != nil {
return err
}
}
for _, d := range s.Directives {
for _, arg := range d.Args {
t, err := common.ResolveType(arg.Type, s.Resolve)
if err != nil {
return err
}
arg.Type = t
}
}
s.EntryPoints = make(map[string]NamedType)
for key, name := range s.entryPointNames {
t, ok := s.Types[name]
if !ok {
if !ok {
return errors.Errorf("type %q not found", name)
}
}
s.EntryPoints[key] = t
}
for _, obj := range s.objects {
obj.Interfaces = make([]*Interface, len(obj.interfaceNames))
for i, intfName := range obj.interfaceNames {
t, ok := s.Types[intfName]
if !ok {
return errors.Errorf("interface %q not found", intfName)
}
intf, ok := t.(*Interface)
if !ok {
return errors.Errorf("type %q is not an interface", intfName)
}
obj.Interfaces[i] = intf
intf.PossibleTypes = append(intf.PossibleTypes, obj)
}
}
for _, union := range s.unions {
union.PossibleTypes = make([]*Object, len(union.typeNames))
for i, name := range union.typeNames {
t, ok := s.Types[name]
if !ok {
return errors.Errorf("object type %q not found", name)
}
obj, ok := t.(*Object)
if !ok {
return errors.Errorf("type %q is not an object", name)
}
union.PossibleTypes[i] = obj
}
}
for _, enum := range s.enums {
for _, value := range enum.Values {
if err := resolveDirectives(s, value.Directives); err != nil {
return err
}
}
}
return nil
}
func resolveNamedType(s *Schema, t NamedType) error {
switch t := t.(type) {
case *Object:
for _, f := range t.Fields {
if err := resolveField(s, f); err != nil {
return err
}
}
case *Interface:
for _, f := range t.Fields {
if err := resolveField(s, f); err != nil {
return err
}
}
case *InputObject:
if err := resolveInputObject(s, t.Values); err != nil {
return err
}
}
return nil
}
func resolveField(s *Schema, f *Field) error {
t, err := common.ResolveType(f.Type, s.Resolve)
if err != nil {
return err
}
f.Type = t
if err := resolveDirectives(s, f.Directives); err != nil {
return err
}
return resolveInputObject(s, f.Args)
}
func resolveDirectives(s *Schema, directives common.DirectiveList) error {
for _, d := range directives {
dirName := d.Name.Name
dd, ok := s.Directives[dirName]
if !ok {
return errors.Errorf("directive %q not found", dirName)
}
for _, arg := range d.Args {
if dd.Args.Get(arg.Name.Name) == nil {
return errors.Errorf("invalid argument %q for directive %q", arg.Name.Name, dirName)
}
}
for _, arg := range dd.Args {
if _, ok := d.Args.Get(arg.Name.Name); !ok {
d.Args = append(d.Args, common.Argument{Name: arg.Name, Value: arg.Default})
}
}
}
return nil
}
func resolveInputObject(s *Schema, values common.InputValueList) error {
for _, v := range values {
t, err := common.ResolveType(v.Type, s.Resolve)
if err != nil {
return err
}
v.Type = t
}
return nil
}
func parseSchema(s *Schema, l *common.Lexer) {
l.Consume()
for l.Peek() != scanner.EOF {
desc := l.DescComment()
switch x := l.ConsumeIdent(); x {
case "schema":
l.ConsumeToken('{')
for l.Peek() != '}' {
name := l.ConsumeIdent()
l.ConsumeToken(':')
typ := l.ConsumeIdent()
s.entryPointNames[name] = typ
}
l.ConsumeToken('}')
case "type":
obj := parseObjectDef(l)
obj.Desc = desc
s.Types[obj.Name] = obj
s.objects = append(s.objects, obj)
case "interface":
iface := parseInterfaceDef(l)
iface.Desc = desc
s.Types[iface.Name] = iface
case "union":
union := parseUnionDef(l)
union.Desc = desc
s.Types[union.Name] = union
s.unions = append(s.unions, union)
case "enum":
enum := parseEnumDef(l)
enum.Desc = desc
s.Types[enum.Name] = enum
s.enums = append(s.enums, enum)
case "input":
input := parseInputDef(l)
input.Desc = desc
s.Types[input.Name] = input
case "scalar":
name := l.ConsumeIdent()
s.Types[name] = &Scalar{Name: name, Desc: desc}
case "directive":
directive := parseDirectiveDef(l)
directive.Desc = desc
s.Directives[directive.Name] = directive
default:
// TODO: Add support for type extensions.
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union", "input", "scalar" or "directive"`, x))
}
}
}
func parseObjectDef(l *common.Lexer) *Object {
object := &Object{Name: l.ConsumeIdent()}
if l.Peek() == scanner.Ident {
l.ConsumeKeyword("implements")
for l.Peek() != '{' {
if l.Peek() == '&' {
l.ConsumeToken('&')
}
object.interfaceNames = append(object.interfaceNames, l.ConsumeIdent())
}
}
l.ConsumeToken('{')
object.Fields = parseFieldsDef(l)
l.ConsumeToken('}')
return object
}
func parseInterfaceDef(l *common.Lexer) *Interface {
i := &Interface{Name: l.ConsumeIdent()}
l.ConsumeToken('{')
i.Fields = parseFieldsDef(l)
l.ConsumeToken('}')
return i
}
func parseUnionDef(l *common.Lexer) *Union {
union := &Union{Name: l.ConsumeIdent()}
l.ConsumeToken('=')
union.typeNames = []string{l.ConsumeIdent()}
for l.Peek() == '|' {
l.ConsumeToken('|')
union.typeNames = append(union.typeNames, l.ConsumeIdent())
}
return union
}
func parseInputDef(l *common.Lexer) *InputObject {
i := &InputObject{}
i.Name = l.ConsumeIdent()
l.ConsumeToken('{')
for l.Peek() != '}' {
i.Values = append(i.Values, common.ParseInputValue(l))
}
l.ConsumeToken('}')
return i
}
func parseEnumDef(l *common.Lexer) *Enum {
enum := &Enum{Name: l.ConsumeIdent()}
l.ConsumeToken('{')
for l.Peek() != '}' {
v := &EnumValue{
Desc: l.DescComment(),
Name: l.ConsumeIdent(),
Directives: common.ParseDirectives(l),
}
enum.Values = append(enum.Values, v)
}
l.ConsumeToken('}')
return enum
}
func parseDirectiveDef(l *common.Lexer) *DirectiveDecl {
l.ConsumeToken('@')
d := &DirectiveDecl{Name: l.ConsumeIdent()}
if l.Peek() == '(' {
l.ConsumeToken('(')
for l.Peek() != ')' {
v := common.ParseInputValue(l)
d.Args = append(d.Args, v)
}
l.ConsumeToken(')')
}
l.ConsumeKeyword("on")
for {
loc := l.ConsumeIdent()
d.Locs = append(d.Locs, loc)
if l.Peek() != '|' {
break
}
l.ConsumeToken('|')
}
return d
}
func parseFieldsDef(l *common.Lexer) FieldList {
var fields FieldList
for l.Peek() != '}' {
f := &Field{}
f.Desc = l.DescComment()
f.Name = l.ConsumeIdent()
if l.Peek() == '(' {
l.ConsumeToken('(')
for l.Peek() != ')' {
f.Args = append(f.Args, common.ParseInputValue(l))
}
l.ConsumeToken(')')
}
l.ConsumeToken(':')
f.Type = common.ParseType(l)
f.Directives = common.ParseDirectives(l)
fields = append(fields, f)
}
return fields
}