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>
239 lines
5.8 KiB
Go
239 lines
5.8 KiB
Go
package selected
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
|
|
"github.com/graph-gophers/graphql-go/errors"
|
|
"github.com/graph-gophers/graphql-go/internal/common"
|
|
"github.com/graph-gophers/graphql-go/internal/exec/packer"
|
|
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
|
|
"github.com/graph-gophers/graphql-go/internal/query"
|
|
"github.com/graph-gophers/graphql-go/internal/schema"
|
|
"github.com/graph-gophers/graphql-go/introspection"
|
|
)
|
|
|
|
type Request struct {
|
|
Schema *schema.Schema
|
|
Doc *query.Document
|
|
Vars map[string]interface{}
|
|
Mu sync.Mutex
|
|
Errs []*errors.QueryError
|
|
}
|
|
|
|
func (r *Request) AddError(err *errors.QueryError) {
|
|
r.Mu.Lock()
|
|
r.Errs = append(r.Errs, err)
|
|
r.Mu.Unlock()
|
|
}
|
|
|
|
func ApplyOperation(r *Request, s *resolvable.Schema, op *query.Operation) []Selection {
|
|
var obj *resolvable.Object
|
|
switch op.Type {
|
|
case query.Query:
|
|
obj = s.Query.(*resolvable.Object)
|
|
case query.Mutation:
|
|
obj = s.Mutation.(*resolvable.Object)
|
|
}
|
|
return applySelectionSet(r, obj, op.Selections)
|
|
}
|
|
|
|
type Selection interface {
|
|
isSelection()
|
|
}
|
|
|
|
type SchemaField struct {
|
|
resolvable.Field
|
|
Alias string
|
|
Args map[string]interface{}
|
|
PackedArgs reflect.Value
|
|
Sels []Selection
|
|
Async bool
|
|
FixedResult reflect.Value
|
|
}
|
|
|
|
type TypeAssertion struct {
|
|
resolvable.TypeAssertion
|
|
Sels []Selection
|
|
}
|
|
|
|
type TypenameField struct {
|
|
resolvable.Object
|
|
Alias string
|
|
}
|
|
|
|
func (*SchemaField) isSelection() {}
|
|
func (*TypeAssertion) isSelection() {}
|
|
func (*TypenameField) isSelection() {}
|
|
|
|
func applySelectionSet(r *Request, e *resolvable.Object, sels []query.Selection) (flattenedSels []Selection) {
|
|
for _, sel := range sels {
|
|
switch sel := sel.(type) {
|
|
case *query.Field:
|
|
field := sel
|
|
if skipByDirective(r, field.Directives) {
|
|
continue
|
|
}
|
|
|
|
switch field.Name.Name {
|
|
case "__typename":
|
|
flattenedSels = append(flattenedSels, &TypenameField{
|
|
Object: *e,
|
|
Alias: field.Alias.Name,
|
|
})
|
|
|
|
case "__schema":
|
|
flattenedSels = append(flattenedSels, &SchemaField{
|
|
Field: resolvable.MetaFieldSchema,
|
|
Alias: field.Alias.Name,
|
|
Sels: applySelectionSet(r, resolvable.MetaSchema, field.Selections),
|
|
Async: true,
|
|
FixedResult: reflect.ValueOf(introspection.WrapSchema(r.Schema)),
|
|
})
|
|
|
|
case "__type":
|
|
p := packer.ValuePacker{ValueType: reflect.TypeOf("")}
|
|
v, err := p.Pack(field.Arguments.MustGet("name").Value(r.Vars))
|
|
if err != nil {
|
|
r.AddError(errors.Errorf("%s", err))
|
|
return nil
|
|
}
|
|
|
|
t, ok := r.Schema.Types[v.String()]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
flattenedSels = append(flattenedSels, &SchemaField{
|
|
Field: resolvable.MetaFieldType,
|
|
Alias: field.Alias.Name,
|
|
Sels: applySelectionSet(r, resolvable.MetaType, field.Selections),
|
|
Async: true,
|
|
FixedResult: reflect.ValueOf(introspection.WrapType(t)),
|
|
})
|
|
|
|
default:
|
|
fe := e.Fields[field.Name.Name]
|
|
|
|
var args map[string]interface{}
|
|
var packedArgs reflect.Value
|
|
if fe.ArgsPacker != nil {
|
|
args = make(map[string]interface{})
|
|
for _, arg := range field.Arguments {
|
|
args[arg.Name.Name] = arg.Value.Value(r.Vars)
|
|
}
|
|
var err error
|
|
packedArgs, err = fe.ArgsPacker.Pack(args)
|
|
if err != nil {
|
|
r.AddError(errors.Errorf("%s", err))
|
|
return
|
|
}
|
|
}
|
|
|
|
fieldSels := applyField(r, fe.ValueExec, field.Selections)
|
|
flattenedSels = append(flattenedSels, &SchemaField{
|
|
Field: *fe,
|
|
Alias: field.Alias.Name,
|
|
Args: args,
|
|
PackedArgs: packedArgs,
|
|
Sels: fieldSels,
|
|
Async: fe.HasContext || fe.ArgsPacker != nil || fe.HasError || HasAsyncSel(fieldSels),
|
|
})
|
|
}
|
|
|
|
case *query.InlineFragment:
|
|
frag := sel
|
|
if skipByDirective(r, frag.Directives) {
|
|
continue
|
|
}
|
|
flattenedSels = append(flattenedSels, applyFragment(r, e, &frag.Fragment)...)
|
|
|
|
case *query.FragmentSpread:
|
|
spread := sel
|
|
if skipByDirective(r, spread.Directives) {
|
|
continue
|
|
}
|
|
flattenedSels = append(flattenedSels, applyFragment(r, e, &r.Doc.Fragments.Get(spread.Name.Name).Fragment)...)
|
|
|
|
default:
|
|
panic("invalid type")
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func applyFragment(r *Request, e *resolvable.Object, frag *query.Fragment) []Selection {
|
|
if frag.On.Name != "" && frag.On.Name != e.Name {
|
|
a, ok := e.TypeAssertions[frag.On.Name]
|
|
if !ok {
|
|
panic(fmt.Errorf("%q does not implement %q", frag.On, e.Name)) // TODO proper error handling
|
|
}
|
|
|
|
return []Selection{&TypeAssertion{
|
|
TypeAssertion: *a,
|
|
Sels: applySelectionSet(r, a.TypeExec.(*resolvable.Object), frag.Selections),
|
|
}}
|
|
}
|
|
return applySelectionSet(r, e, frag.Selections)
|
|
}
|
|
|
|
func applyField(r *Request, e resolvable.Resolvable, sels []query.Selection) []Selection {
|
|
switch e := e.(type) {
|
|
case *resolvable.Object:
|
|
return applySelectionSet(r, e, sels)
|
|
case *resolvable.List:
|
|
return applyField(r, e.Elem, sels)
|
|
case *resolvable.Scalar:
|
|
return nil
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
func skipByDirective(r *Request, directives common.DirectiveList) bool {
|
|
if d := directives.Get("skip"); d != nil {
|
|
p := packer.ValuePacker{ValueType: reflect.TypeOf(false)}
|
|
v, err := p.Pack(d.Args.MustGet("if").Value(r.Vars))
|
|
if err != nil {
|
|
r.AddError(errors.Errorf("%s", err))
|
|
}
|
|
if err == nil && v.Bool() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if d := directives.Get("include"); d != nil {
|
|
p := packer.ValuePacker{ValueType: reflect.TypeOf(false)}
|
|
v, err := p.Pack(d.Args.MustGet("if").Value(r.Vars))
|
|
if err != nil {
|
|
r.AddError(errors.Errorf("%s", err))
|
|
}
|
|
if err == nil && !v.Bool() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func HasAsyncSel(sels []Selection) bool {
|
|
for _, sel := range sels {
|
|
switch sel := sel.(type) {
|
|
case *SchemaField:
|
|
if sel.Async {
|
|
return true
|
|
}
|
|
case *TypeAssertion:
|
|
if HasAsyncSel(sel.Sels) {
|
|
return true
|
|
}
|
|
case *TypenameField:
|
|
// sync
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
return false
|
|
}
|