mirror of
https://github.com/cwinfo/matterbridge.git
synced 2024-11-23 11:41:35 +00:00
270 lines
7.0 KiB
Go
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
|
||
|
}
|