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
|
||
|
}
|