Skip to content

Commit

Permalink
execs
Browse files Browse the repository at this point in the history
  • Loading branch information
neelance committed Oct 17, 2016
1 parent d70d16c commit 08f02a2
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 114 deletions.
118 changes: 4 additions & 114 deletions graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,14 @@ package graphql
import (
"encoding/json"
"fmt"
"reflect"
"strings"

"github.com/neelance/graphql-go/internal/exec"
"github.com/neelance/graphql-go/internal/query"
"github.com/neelance/graphql-go/internal/schema"
)

type Schema struct {
*schema.Schema
resolver reflect.Value
}

type request struct {
*query.Document
Variables map[string]interface{}
exec *exec.Exec
}

func NewSchema(schemaString string, filename string, resolver interface{}) (*Schema, error) {
Expand All @@ -26,10 +19,8 @@ func NewSchema(schemaString string, filename string, resolver interface{}) (*Sch
return nil, err
}

// TODO type check resolver
return &Schema{
Schema: s,
resolver: reflect.ValueOf(resolver),
exec: exec.Make(s, resolver),
}, nil
}

Expand All @@ -50,107 +41,6 @@ func (s *Schema) Exec(queryString string, operationName string, variables map[st
return nil, fmt.Errorf("no operation with name %q", operationName)
}

r := &request{Document: d, Variables: variables}
rawRes := exec(s, r, s.Types[s.EntryPoints["query"]], op.SelSet, s.resolver)
rawRes := s.exec.Exec(d, variables, op.SelSet)
return json.Marshal(rawRes)
}

func exec(s *Schema, r *request, t schema.Type, selSet *query.SelectionSet, resolver reflect.Value) interface{} {
switch t := t.(type) {
case *schema.Scalar:
return resolver.Interface()

case *schema.Object:
result := make(map[string]interface{})
execSelectionSet(s, r, t, selSet, resolver, result)
return result

case *schema.Enum:
return resolver.Interface()

case *schema.List:
a := make([]interface{}, resolver.Len())
for i := range a {
a[i] = exec(s, r, t.Elem, selSet, resolver.Index(i))
}
return a

case *schema.TypeReference:
return exec(s, r, s.Types[t.Name], selSet, resolver)

default:
panic("invalid type")
}
}

func execSelectionSet(s *Schema, r *request, t *schema.Object, selSet *query.SelectionSet, resolver reflect.Value, result map[string]interface{}) {
for _, sel := range selSet.Selections {
switch sel := sel.(type) {
case *query.Field:
if skipByDirective(r, sel.Directives) {
continue
}
execField(s, r, t, sel, resolver, result)
case *query.FragmentSpread:
if skipByDirective(r, sel.Directives) {
continue
}
execSelectionSet(s, r, t, r.Fragments[sel.Name].SelSet, resolver, result)
default:
panic("invalid type")
}
}
}

func execField(s *Schema, r *request, t *schema.Object, f *query.Field, resolver reflect.Value, result map[string]interface{}) {
sf := t.Fields[f.Name]
m := resolver.Method(findMethod(resolver.Type(), f.Name))
var in []reflect.Value
if len(sf.Parameters) != 0 {
args := reflect.New(m.Type().In(0))
for name, param := range sf.Parameters {
value, ok := f.Arguments[name]
if !ok {
value = &query.Literal{Value: param.Default}
}
rf := args.Elem().FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) })
rf.Set(reflect.ValueOf(execValue(r, value)))
}
in = []reflect.Value{args.Elem()}
}
result[f.Alias] = exec(s, r, sf.Type, f.SelSet, m.Call(in)[0])
}

func skipByDirective(r *request, d map[string]*query.Directive) bool {
if skip, ok := d["skip"]; ok {
if execValue(r, skip.Arguments["if"]).(bool) {
return true
}
}
if include, ok := d["include"]; ok {
if !execValue(r, include.Arguments["if"]).(bool) {
return true
}
}
return false
}

func execValue(r *request, v query.Value) interface{} {
switch v := v.(type) {
case *query.Variable:
return r.Variables[v.Name]
case *query.Literal:
return v.Value
default:
panic("invalid value")
}
}

func findMethod(t reflect.Type, name string) int {
for i := 0; i < t.NumMethod(); i++ {
if strings.EqualFold(name, t.Method(i).Name) {
return i
}
}
return -1
}
195 changes: 195 additions & 0 deletions internal/exec/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package exec

import (
"reflect"
"strings"

"github.com/neelance/graphql-go/internal/query"
"github.com/neelance/graphql-go/internal/schema"
)

type Exec struct {
iExec
resolver reflect.Value
}

func Make(s *schema.Schema, resolver interface{}) *Exec {
t := s.Types[s.EntryPoints["query"]]
return &Exec{
iExec: makeExec(s, t, reflect.TypeOf(resolver), make(map[typeRefMapKey]*typeRefExec)),
resolver: reflect.ValueOf(resolver),
}
}

func (e *Exec) Exec(document *query.Document, variables map[string]interface{}, selSet *query.SelectionSet) interface{} {
return e.exec(&request{document, variables}, selSet, e.resolver)
}

func makeExec(s *schema.Schema, t schema.Type, resolverType reflect.Type, typeRefMap map[typeRefMapKey]*typeRefExec) iExec {
switch t := t.(type) {
case *schema.Scalar:
return &scalarExec{}

case *schema.Object:
fields := make(map[string]*fieldExec)

for name, f := range t.Fields {
methodIndex := -1
for i := 0; i < resolverType.NumMethod(); i++ {
if strings.EqualFold(name, resolverType.Method(i).Name) {
methodIndex = i
break
}
}
if methodIndex == -1 {
continue // TODO error
}

fields[name] = &fieldExec{
field: f,
methodIndex: methodIndex,
valueExec: makeExec(s, f.Type, resolverType.Method(methodIndex).Type.Out(0), typeRefMap),
}
}

return &objectExec{
fields: fields,
}

case *schema.Enum:
return &scalarExec{}

case *schema.List:
return &listExec{
elem: makeExec(s, t.Elem, resolverType.Elem(), typeRefMap),
}

case *schema.TypeReference:
refT := s.Types[t.Name]
k := typeRefMapKey{refT, resolverType}
e, ok := typeRefMap[k]
if !ok {
e = &typeRefExec{}
typeRefMap[k] = e
e.iExec = makeExec(s, refT, resolverType, typeRefMap)
}
return e

default:
panic("invalid type")
}
}

type request struct {
*query.Document
Variables map[string]interface{}
}

type iExec interface {
exec(r *request, selSet *query.SelectionSet, resolver reflect.Value) interface{}
}

type scalarExec struct{}

func (e *scalarExec) exec(r *request, selSet *query.SelectionSet, resolver reflect.Value) interface{} {
return resolver.Interface()
}

type listExec struct {
elem iExec
}

func (e *listExec) exec(r *request, selSet *query.SelectionSet, resolver reflect.Value) interface{} {
l := make([]interface{}, resolver.Len())
for i := range l {
l[i] = e.elem.exec(r, selSet, resolver.Index(i))
}
return l
}

type typeRefExec struct {
iExec
}

type typeRefMapKey struct {
s schema.Type
r reflect.Type
}

type objectExec struct {
fields map[string]*fieldExec
}

func (e *objectExec) exec(r *request, selSet *query.SelectionSet, resolver reflect.Value) interface{} {
result := make(map[string]interface{})
e.execFragment(r, selSet, resolver, result)
return result
}

func (e *objectExec) execFragment(r *request, selSet *query.SelectionSet, resolver reflect.Value, result map[string]interface{}) {
for _, sel := range selSet.Selections {
switch sel := sel.(type) {
case *query.Field:
if skipByDirective(r, sel.Directives) {
continue
}
e.fields[sel.Name].exec(r, sel, resolver, result)
case *query.FragmentSpread:
if skipByDirective(r, sel.Directives) {
continue
}
e.execFragment(r, r.Fragments[sel.Name].SelSet, resolver, result)
default:
panic("invalid type")
}
}
}

type fieldExec struct {
field *schema.Field
methodIndex int
valueExec iExec
}

func (e *fieldExec) exec(r *request, f *query.Field, resolver reflect.Value, result map[string]interface{}) {
m := resolver.Method(e.methodIndex)
var in []reflect.Value
if len(e.field.Parameters) != 0 {
args := reflect.New(m.Type().In(0))
for name, param := range e.field.Parameters {
value, ok := f.Arguments[name]
if !ok {
value = &query.Literal{Value: param.Default}
}
rf := args.Elem().FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) })
rf.Set(reflect.ValueOf(execValue(r, value)))
}
in = []reflect.Value{args.Elem()}
}
result[f.Alias] = e.valueExec.exec(r, f.SelSet, m.Call(in)[0])
}

func skipByDirective(r *request, d map[string]*query.Directive) bool {
if skip, ok := d["skip"]; ok {
if execValue(r, skip.Arguments["if"]).(bool) {
return true
}
}
if include, ok := d["include"]; ok {
if !execValue(r, include.Arguments["if"]).(bool) {
return true
}
}
return false
}

func execValue(r *request, v query.Value) interface{} {
switch v := v.(type) {
case *query.Variable:
return r.Variables[v.Name]
case *query.Literal:
return v.Value
default:
panic("invalid value")
}
}

0 comments on commit 08f02a2

Please sign in to comment.