matterbridge/vendor/github.com/graph-gophers/graphql-go/internal/exec/selected/selected.go
2022-04-01 00:23:19 +02:00

270 lines
7.0 KiB
Go

package selected
import (
"fmt"
"reflect"
"sync"
"github.com/graph-gophers/graphql-go/errors"
"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/introspection"
"github.com/graph-gophers/graphql-go/types"
)
type Request struct {
Schema *types.Schema
Doc *types.ExecutableDefinition
Vars map[string]interface{}
Mu sync.Mutex
Errs []*errors.QueryError
DisableIntrospection bool
}
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 *types.OperationDefinition) []Selection {
var obj *resolvable.Object
switch op.Type {
case query.Query:
obj = s.Query.(*resolvable.Object)
case query.Mutation:
obj = s.Mutation.(*resolvable.Object)
case query.Subscription:
obj = s.Subscription.(*resolvable.Object)
}
return applySelectionSet(r, s, 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, s *resolvable.Schema, e *resolvable.Object, sels []types.Selection) (flattenedSels []Selection) {
for _, sel := range sels {
switch sel := sel.(type) {
case *types.Field:
field := sel
if skipByDirective(r, field.Directives) {
continue
}
switch field.Name.Name {
case "__typename":
// __typename is available even though r.DisableIntrospection == true
// because it is necessary when using union types and interfaces: https://graphql.org/learn/schema/#union-types
flattenedSels = append(flattenedSels, &TypenameField{
Object: *e,
Alias: field.Alias.Name,
})
case "__schema":
if !r.DisableIntrospection {
flattenedSels = append(flattenedSels, &SchemaField{
Field: s.Meta.FieldSchema,
Alias: field.Alias.Name,
Sels: applySelectionSet(r, s, s.Meta.Schema, field.SelectionSet),
Async: true,
FixedResult: reflect.ValueOf(introspection.WrapSchema(r.Schema)),
})
}
case "__type":
if !r.DisableIntrospection {
p := packer.ValuePacker{ValueType: reflect.TypeOf("")}
v, err := p.Pack(field.Arguments.MustGet("name").Deserialize(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: s.Meta.FieldType,
Alias: field.Alias.Name,
Sels: applySelectionSet(r, s, s.Meta.Type, field.SelectionSet),
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.Deserialize(r.Vars)
}
var err error
packedArgs, err = fe.ArgsPacker.Pack(args)
if err != nil {
r.AddError(errors.Errorf("%s", err))
return
}
}
fieldSels := applyField(r, s, fe.ValueExec, field.SelectionSet)
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 *types.InlineFragment:
frag := sel
if skipByDirective(r, frag.Directives) {
continue
}
flattenedSels = append(flattenedSels, applyFragment(r, s, e, &frag.Fragment)...)
case *types.FragmentSpread:
spread := sel
if skipByDirective(r, spread.Directives) {
continue
}
flattenedSels = append(flattenedSels, applyFragment(r, s, e, &r.Doc.Fragments.Get(spread.Name.Name).Fragment)...)
default:
panic("invalid type")
}
}
return
}
func applyFragment(r *Request, s *resolvable.Schema, e *resolvable.Object, frag *types.Fragment) []Selection {
if frag.On.Name != e.Name {
t := r.Schema.Resolve(frag.On.Name)
face, ok := t.(*types.InterfaceTypeDefinition)
if !ok && frag.On.Name != "" {
a, ok2 := e.TypeAssertions[frag.On.Name]
if !ok2 {
panic(fmt.Errorf("%q does not implement %q", frag.On, e.Name)) // TODO proper error handling
}
return []Selection{&TypeAssertion{
TypeAssertion: *a,
Sels: applySelectionSet(r, s, a.TypeExec.(*resolvable.Object), frag.Selections),
}}
}
if ok && len(face.PossibleTypes) > 0 {
sels := []Selection{}
for _, t := range face.PossibleTypes {
if t.Name == e.Name {
return applySelectionSet(r, s, e, frag.Selections)
}
if a, ok := e.TypeAssertions[t.Name]; ok {
sels = append(sels, &TypeAssertion{
TypeAssertion: *a,
Sels: applySelectionSet(r, s, a.TypeExec.(*resolvable.Object), frag.Selections),
})
}
}
if len(sels) == 0 {
panic(fmt.Errorf("%q does not implement %q", e.Name, frag.On)) // TODO proper error handling
}
return sels
}
}
return applySelectionSet(r, s, e, frag.Selections)
}
func applyField(r *Request, s *resolvable.Schema, e resolvable.Resolvable, sels []types.Selection) []Selection {
switch e := e.(type) {
case *resolvable.Object:
return applySelectionSet(r, s, e, sels)
case *resolvable.List:
return applyField(r, s, e.Elem, sels)
case *resolvable.Scalar:
return nil
default:
panic("unreachable")
}
}
func skipByDirective(r *Request, directives types.DirectiveList) bool {
if d := directives.Get("skip"); d != nil {
p := packer.ValuePacker{ValueType: reflect.TypeOf(false)}
v, err := p.Pack(d.Arguments.MustGet("if").Deserialize(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.Arguments.MustGet("if").Deserialize(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
}