f91312dbdb
* 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>
332 lines
7.9 KiB
Go
332 lines
7.9 KiB
Go
package resolvable
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/graph-gophers/graphql-go/internal/common"
|
|
"github.com/graph-gophers/graphql-go/internal/exec/packer"
|
|
"github.com/graph-gophers/graphql-go/internal/schema"
|
|
)
|
|
|
|
type Schema struct {
|
|
schema.Schema
|
|
Query Resolvable
|
|
Mutation Resolvable
|
|
Resolver reflect.Value
|
|
}
|
|
|
|
type Resolvable interface {
|
|
isResolvable()
|
|
}
|
|
|
|
type Object struct {
|
|
Name string
|
|
Fields map[string]*Field
|
|
TypeAssertions map[string]*TypeAssertion
|
|
}
|
|
|
|
type Field struct {
|
|
schema.Field
|
|
TypeName string
|
|
MethodIndex int
|
|
HasContext bool
|
|
HasError bool
|
|
ArgsPacker *packer.StructPacker
|
|
ValueExec Resolvable
|
|
TraceLabel string
|
|
}
|
|
|
|
type TypeAssertion struct {
|
|
MethodIndex int
|
|
TypeExec Resolvable
|
|
}
|
|
|
|
type List struct {
|
|
Elem Resolvable
|
|
}
|
|
|
|
type Scalar struct{}
|
|
|
|
func (*Object) isResolvable() {}
|
|
func (*List) isResolvable() {}
|
|
func (*Scalar) isResolvable() {}
|
|
|
|
func ApplyResolver(s *schema.Schema, resolver interface{}) (*Schema, error) {
|
|
b := newBuilder(s)
|
|
|
|
var query, mutation Resolvable
|
|
|
|
if t, ok := s.EntryPoints["query"]; ok {
|
|
if err := b.assignExec(&query, t, reflect.TypeOf(resolver)); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if t, ok := s.EntryPoints["mutation"]; ok {
|
|
if err := b.assignExec(&mutation, t, reflect.TypeOf(resolver)); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err := b.finish(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Schema{
|
|
Schema: *s,
|
|
Resolver: reflect.ValueOf(resolver),
|
|
Query: query,
|
|
Mutation: mutation,
|
|
}, nil
|
|
}
|
|
|
|
type execBuilder struct {
|
|
schema *schema.Schema
|
|
resMap map[typePair]*resMapEntry
|
|
packerBuilder *packer.Builder
|
|
}
|
|
|
|
type typePair struct {
|
|
graphQLType common.Type
|
|
resolverType reflect.Type
|
|
}
|
|
|
|
type resMapEntry struct {
|
|
exec Resolvable
|
|
targets []*Resolvable
|
|
}
|
|
|
|
func newBuilder(s *schema.Schema) *execBuilder {
|
|
return &execBuilder{
|
|
schema: s,
|
|
resMap: make(map[typePair]*resMapEntry),
|
|
packerBuilder: packer.NewBuilder(),
|
|
}
|
|
}
|
|
|
|
func (b *execBuilder) finish() error {
|
|
for _, entry := range b.resMap {
|
|
for _, target := range entry.targets {
|
|
*target = entry.exec
|
|
}
|
|
}
|
|
|
|
return b.packerBuilder.Finish()
|
|
}
|
|
|
|
func (b *execBuilder) assignExec(target *Resolvable, t common.Type, resolverType reflect.Type) error {
|
|
k := typePair{t, resolverType}
|
|
ref, ok := b.resMap[k]
|
|
if !ok {
|
|
ref = &resMapEntry{}
|
|
b.resMap[k] = ref
|
|
var err error
|
|
ref.exec, err = b.makeExec(t, resolverType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
ref.targets = append(ref.targets, target)
|
|
return nil
|
|
}
|
|
|
|
func (b *execBuilder) makeExec(t common.Type, resolverType reflect.Type) (Resolvable, error) {
|
|
var nonNull bool
|
|
t, nonNull = unwrapNonNull(t)
|
|
|
|
switch t := t.(type) {
|
|
case *schema.Object:
|
|
return b.makeObjectExec(t.Name, t.Fields, nil, nonNull, resolverType)
|
|
|
|
case *schema.Interface:
|
|
return b.makeObjectExec(t.Name, t.Fields, t.PossibleTypes, nonNull, resolverType)
|
|
|
|
case *schema.Union:
|
|
return b.makeObjectExec(t.Name, nil, t.PossibleTypes, nonNull, resolverType)
|
|
}
|
|
|
|
if !nonNull {
|
|
if resolverType.Kind() != reflect.Ptr {
|
|
return nil, fmt.Errorf("%s is not a pointer", resolverType)
|
|
}
|
|
resolverType = resolverType.Elem()
|
|
}
|
|
|
|
switch t := t.(type) {
|
|
case *schema.Scalar:
|
|
return makeScalarExec(t, resolverType)
|
|
|
|
case *schema.Enum:
|
|
return &Scalar{}, nil
|
|
|
|
case *common.List:
|
|
if resolverType.Kind() != reflect.Slice {
|
|
return nil, fmt.Errorf("%s is not a slice", resolverType)
|
|
}
|
|
e := &List{}
|
|
if err := b.assignExec(&e.Elem, t.OfType, resolverType.Elem()); err != nil {
|
|
return nil, err
|
|
}
|
|
return e, nil
|
|
|
|
default:
|
|
panic("invalid type: " + t.String())
|
|
}
|
|
}
|
|
|
|
func makeScalarExec(t *schema.Scalar, resolverType reflect.Type) (Resolvable, error) {
|
|
implementsType := false
|
|
switch r := reflect.New(resolverType).Interface().(type) {
|
|
case *int32:
|
|
implementsType = (t.Name == "Int")
|
|
case *float64:
|
|
implementsType = (t.Name == "Float")
|
|
case *string:
|
|
implementsType = (t.Name == "String")
|
|
case *bool:
|
|
implementsType = (t.Name == "Boolean")
|
|
case packer.Unmarshaler:
|
|
implementsType = r.ImplementsGraphQLType(t.Name)
|
|
}
|
|
if !implementsType {
|
|
return nil, fmt.Errorf("can not use %s as %s", resolverType, t.Name)
|
|
}
|
|
return &Scalar{}, nil
|
|
}
|
|
|
|
func (b *execBuilder) makeObjectExec(typeName string, fields schema.FieldList, possibleTypes []*schema.Object, nonNull bool, resolverType reflect.Type) (*Object, error) {
|
|
if !nonNull {
|
|
if resolverType.Kind() != reflect.Ptr && resolverType.Kind() != reflect.Interface {
|
|
return nil, fmt.Errorf("%s is not a pointer or interface", resolverType)
|
|
}
|
|
}
|
|
|
|
methodHasReceiver := resolverType.Kind() != reflect.Interface
|
|
|
|
Fields := make(map[string]*Field)
|
|
for _, f := range fields {
|
|
methodIndex := findMethod(resolverType, f.Name)
|
|
if methodIndex == -1 {
|
|
hint := ""
|
|
if findMethod(reflect.PtrTo(resolverType), f.Name) != -1 {
|
|
hint = " (hint: the method exists on the pointer type)"
|
|
}
|
|
return nil, fmt.Errorf("%s does not resolve %q: missing method for field %q%s", resolverType, typeName, f.Name, hint)
|
|
}
|
|
|
|
m := resolverType.Method(methodIndex)
|
|
fe, err := b.makeFieldExec(typeName, f, m, methodIndex, methodHasReceiver)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s\n\treturned by (%s).%s", err, resolverType, m.Name)
|
|
}
|
|
Fields[f.Name] = fe
|
|
}
|
|
|
|
typeAssertions := make(map[string]*TypeAssertion)
|
|
for _, impl := range possibleTypes {
|
|
methodIndex := findMethod(resolverType, "To"+impl.Name)
|
|
if methodIndex == -1 {
|
|
return nil, fmt.Errorf("%s does not resolve %q: missing method %q to convert to %q", resolverType, typeName, "To"+impl.Name, impl.Name)
|
|
}
|
|
if resolverType.Method(methodIndex).Type.NumOut() != 2 {
|
|
return nil, fmt.Errorf("%s does not resolve %q: method %q should return a value and a bool indicating success", resolverType, typeName, "To"+impl.Name)
|
|
}
|
|
a := &TypeAssertion{
|
|
MethodIndex: methodIndex,
|
|
}
|
|
if err := b.assignExec(&a.TypeExec, impl, resolverType.Method(methodIndex).Type.Out(0)); err != nil {
|
|
return nil, err
|
|
}
|
|
typeAssertions[impl.Name] = a
|
|
}
|
|
|
|
return &Object{
|
|
Name: typeName,
|
|
Fields: Fields,
|
|
TypeAssertions: typeAssertions,
|
|
}, nil
|
|
}
|
|
|
|
var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
|
|
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
|
|
|
func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.Method, methodIndex int, methodHasReceiver bool) (*Field, error) {
|
|
in := make([]reflect.Type, m.Type.NumIn())
|
|
for i := range in {
|
|
in[i] = m.Type.In(i)
|
|
}
|
|
if methodHasReceiver {
|
|
in = in[1:] // first parameter is receiver
|
|
}
|
|
|
|
hasContext := len(in) > 0 && in[0] == contextType
|
|
if hasContext {
|
|
in = in[1:]
|
|
}
|
|
|
|
var argsPacker *packer.StructPacker
|
|
if len(f.Args) > 0 {
|
|
if len(in) == 0 {
|
|
return nil, fmt.Errorf("must have parameter for field arguments")
|
|
}
|
|
var err error
|
|
argsPacker, err = b.packerBuilder.MakeStructPacker(f.Args, in[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
in = in[1:]
|
|
}
|
|
|
|
if len(in) > 0 {
|
|
return nil, fmt.Errorf("too many parameters")
|
|
}
|
|
|
|
if m.Type.NumOut() > 2 {
|
|
return nil, fmt.Errorf("too many return values")
|
|
}
|
|
|
|
hasError := m.Type.NumOut() == 2
|
|
if hasError {
|
|
if m.Type.Out(1) != errorType {
|
|
return nil, fmt.Errorf(`must have "error" as its second return value`)
|
|
}
|
|
}
|
|
|
|
fe := &Field{
|
|
Field: *f,
|
|
TypeName: typeName,
|
|
MethodIndex: methodIndex,
|
|
HasContext: hasContext,
|
|
ArgsPacker: argsPacker,
|
|
HasError: hasError,
|
|
TraceLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name),
|
|
}
|
|
if err := b.assignExec(&fe.ValueExec, f.Type, m.Type.Out(0)); err != nil {
|
|
return nil, err
|
|
}
|
|
return fe, nil
|
|
}
|
|
|
|
func findMethod(t reflect.Type, name string) int {
|
|
for i := 0; i < t.NumMethod(); i++ {
|
|
if strings.EqualFold(stripUnderscore(name), stripUnderscore(t.Method(i).Name)) {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func unwrapNonNull(t common.Type) (common.Type, bool) {
|
|
if nn, ok := t.(*common.NonNull); ok {
|
|
return nn.OfType, true
|
|
}
|
|
return t, false
|
|
}
|
|
|
|
func stripUnderscore(s string) string {
|
|
return strings.Replace(s, "_", "", -1)
|
|
}
|